From ff611338f932456171883874ccfb98043c2679e1 Mon Sep 17 00:00:00 2001 From: Mick Letofsky Date: Tue, 4 Nov 2025 15:20:54 +0100 Subject: [PATCH 001/249] [PM-4735] - Refactor event handling of the browser close listener (#16798) --- .../services/browser-fido2-user-interface.service.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts index 8de48a49a8e..5e523a1a48d 100644 --- a/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/autofill/fido2/services/browser-fido2-user-interface.service.ts @@ -6,7 +6,7 @@ import { filter, firstValueFrom, fromEvent, - fromEventPattern, + map, merge, Observable, Subject, @@ -28,6 +28,7 @@ import { import { Utils } from "@bitwarden/common/platform/misc/utils"; import { BrowserApi } from "../../../platform/browser/browser-api"; +import { fromChromeEvent } from "../../../platform/browser/from-chrome-event"; // FIXME (PM-22628): Popup imports are forbidden in background // eslint-disable-next-line no-restricted-imports import { closeFido2Popout, openFido2Popout } from "../../../vault/popup/utils/vault-popout-window"; @@ -232,12 +233,8 @@ export class BrowserFido2UserInterfaceSession implements Fido2UserInterfaceSessi } }); - this.windowClosed$ = fromEventPattern( - // FIXME: Make sure that is does not cause a memory leak in Safari or use BrowserApi.AddListener - // and test that it doesn't break. Tracking Ticket: https://bitwarden.atlassian.net/browse/PM-4735 - // eslint-disable-next-line no-restricted-syntax - (handler: any) => chrome.windows.onRemoved.addListener(handler), - (handler: any) => chrome.windows.onRemoved.removeListener(handler), + this.windowClosed$ = fromChromeEvent(chrome.windows.onRemoved).pipe( + map(([windowId]) => windowId), ); BrowserFido2UserInterfaceSession.sendMessage({ From 9803cc98b4c05dfa930df77a385796ec41062fe8 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 4 Nov 2025 06:35:34 -0800 Subject: [PATCH 002/249] Desktop Native remove `log` deps (#17135) --- apps/desktop/desktop_native/Cargo.lock | 35 ++----------------- apps/desktop/desktop_native/Cargo.toml | 3 -- .../desktop_native/macos_provider/Cargo.toml | 3 +- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 9a69ca62a1f..18ea0337a04 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -900,19 +900,6 @@ dependencies = [ "syn", ] -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "der" version = "0.7.10" @@ -1533,12 +1520,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.3" @@ -1554,7 +1535,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.3", + "hashbrown", ] [[package]] @@ -1719,7 +1700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.3", + "hashbrown", ] [[package]] @@ -1891,7 +1872,6 @@ version = "0.0.0" dependencies = [ "desktop_core", "futures", - "oslog", "serde", "serde_json", "tokio", @@ -2379,17 +2359,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "oslog" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" -dependencies = [ - "cc", - "dashmap", - "log", -] - [[package]] name = "p256" version = "0.13.2" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 4b5c1335c6b..edc15675c86 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -41,13 +41,11 @@ interprocess = "=2.2.1" keytar = "=0.1.6" libc = "=0.2.172" linux-keyutils = "=0.2.4" -log = "=0.4.25" memsec = "=0.7.0" napi = "=2.16.17" napi-build = "=2.2.0" napi-derive = "=2.16.13" oo7 = "=0.4.3" -oslog = "=0.2.0" pin-project = "=1.1.10" pkcs8 = "=0.10.2" rand = "=0.9.1" @@ -60,7 +58,6 @@ security-framework-sys = "=2.15.0" serde = "=1.0.209" serde_json = "=1.0.127" sha2 = "=0.10.8" -simplelog = "=0.12.2" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.35.0" diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index 97a8b7d545a..ea44f3d9a27 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -21,12 +21,11 @@ serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } tokio-util = { workspace = true } tracing = { workspace = true } -tracing-oslog = "0.3.0" tracing-subscriber = { workspace = true } uniffi = { workspace = true, features = ["cli"] } [target.'cfg(target_os = "macos")'.dependencies] -oslog = { workspace = true } +tracing-oslog = "0.3.0" [build-dependencies] uniffi = { workspace = true, features = ["build"] } From 9dfc0fe14f200d3b3922f568f6e6d8a1003e2919 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 4 Nov 2025 15:46:19 +0100 Subject: [PATCH 003/249] Fix broken build on latest SDK (#17208) * Fix broken build on latest SDK * Fix test --- .../src/vault/models/domain/cipher.spec.ts | 1 + libs/common/src/vault/models/domain/cipher.ts | 1 + package-lock.json | 16 ++++++++-------- package.json | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 7503d71573f..87301928c57 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -1088,6 +1088,7 @@ describe("Cipher DTO", () => { card: undefined, secureNote: undefined, sshKey: undefined, + data: undefined, favorite: false, reprompt: SdkCipherRepromptType.None, organizationUseTotp: true, diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 5e284232936..5739a9a50a7 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -421,6 +421,7 @@ export class Cipher extends Domain implements Decryptable { card: undefined, secureNote: undefined, sshKey: undefined, + data: undefined, }; switch (this.type) { diff --git a/package-lock.json b/package-lock.json index c8f825319e4..9636184c5ee 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.365", - "@bitwarden/sdk-internal": "0.2.0-main.365", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.369", + "@bitwarden/sdk-internal": "0.2.0-main.369", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4607,9 +4607,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.365", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.365.tgz", - "integrity": "sha512-yRc2k29rKMxss6qH2TP91VcE6tNR6/A2ASZMj+Om2MEaanV82zcx89dkShh6RP0jXICM+c/m6BgGkmu+1Pcp8w==", + "version": "0.2.0-main.369", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.369.tgz", + "integrity": "sha512-O+EaPQJQah9j3yWzgw+dwFk5iOxPXdKf1FDeykbt+cxygSYbWTR60RXenG1LysknOdy8fiTfHEaPD+LP1LxrdA==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4712,9 +4712,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.365", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.365.tgz", - "integrity": "sha512-x0sqAuyknFOGf5ZfbuFTxL0olMiGyyLbJ10tXCYHnrkjdspdNm2BGZc64NQgXz5h+PH1Uwtow/01o/a4F0YTHw==", + "version": "0.2.0-main.369", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.369.tgz", + "integrity": "sha512-gyp4Wd1YbkANA0/RNxHfVk+DuiJqxItzk/YUyQ2HsLeP07xOljftmA0XspLQz59ovs7e1jHMCpH1r/XcyKiQSw==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 181e003bf28..c1becca3a31 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.365", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.365", + "@bitwarden/sdk-internal": "0.2.0-main.369", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.369", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From a13a81ba81a1428cde7055956f84c8cd79a7d363 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 4 Nov 2025 09:56:12 -0500 Subject: [PATCH 004/249] [PM-26984] Use medium instead of semibold or bold (#17193) --- .../key-connector/confirm-key-connector-domain.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html index 6cf151d4604..11b34a8409f 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html @@ -9,7 +9,7 @@ } @else {
-

{{ "keyConnectorDomain" | i18n }}:

+

{{ "keyConnectorDomain" | i18n }}:

{{ keyConnectorUrl }}

From 573d4219f9abad0f84a8b88a2725cc6448fd5ca8 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 4 Nov 2025 10:18:53 -0500 Subject: [PATCH 005/249] [PM-26984] Use medium instead of semibold or bold (#17192) --- apps/desktop/src/platform/components/approve-ssh-request.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index b7005872f25..55092788079 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -1,6 +1,6 @@
-
{{ "sshkeyApprovalTitle" | i18n }}
+
{{ "sshkeyApprovalTitle" | i18n }}
Date: Tue, 4 Nov 2025 10:29:31 -0500 Subject: [PATCH 006/249] [PM-26984] Use medium instead of semibold or bold (#17187) --- .../popup/send-v2/send-created/send-created.component.html | 2 +- apps/web/src/app/tools/send/send.component.html | 2 +- libs/importer/src/components/import.component.html | 6 +++--- .../send-list-items-container.component.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html index f0d66bd49ed..16711fabbf4 100644 --- a/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-created/send-created.component.html @@ -17,7 +17,7 @@
-

+

{{ "createdSendSuccessfully" | i18n }}

diff --git a/apps/web/src/app/tools/send/send.component.html b/apps/web/src/app/tools/send/send.component.html index b79f50311ed..b8538606aec 100644 --- a/apps/web/src/app/tools/send/send.component.html +++ b/apps/web/src/app/tools/send/send.component.html @@ -21,7 +21,7 @@

{{ "filters" | i18n }} diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index 3bd4b741dbb..bd4afaf364b 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -11,7 +11,7 @@ -

{{ "destination" | i18n }}

+

{{ "destination" | i18n }}

@@ -62,7 +62,7 @@ -

{{ "data" | i18n }}

+

{{ "data" | i18n }}

@@ -70,7 +70,7 @@ -

+

{{ headerText }}

{{ sends.length }} From 7801fd312356d27a1d09040e66cb2f6f12ee94f3 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Tue, 4 Nov 2025 08:13:08 -0800 Subject: [PATCH 007/249] Desktop Autotype use managed object for IPC vault data payload (#17105) * Desktop Autotype use managed object for IPC vault data payload * claude feedback 1 * check for falsey values * add unit test * remove unecessary async * helper result type * dear claude --- .../main/main-desktop-autotype.service.ts | 20 +++----- .../autofill/models/autotype-vault-data.ts | 8 +++ apps/desktop/src/autofill/preload.ts | 17 +++---- .../services/desktop-autotype.service.spec.ts | 50 +++++++++++++++++++ .../services/desktop-autotype.service.ts | 31 ++++++++++-- 5 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 apps/desktop/src/autofill/models/autotype-vault-data.ts create mode 100644 apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts diff --git a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts index 09f03d2ef8e..e33ab0d4c3b 100644 --- a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts @@ -5,6 +5,7 @@ import { LogService } from "@bitwarden/logging"; import { WindowMain } from "../../main/window.main"; import { stringIsNotUndefinedNullAndEmpty } from "../../utils"; +import { AutotypeVaultData } from "../models/autotype-vault-data"; import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut"; export class MainDesktopAutotypeService { @@ -47,18 +48,12 @@ export class MainDesktopAutotypeService { } }); - ipcMain.on("autofill.completeAutotypeRequest", (event, data) => { - const { response } = data; - + ipcMain.on("autofill.completeAutotypeRequest", (_event, vaultData: AutotypeVaultData) => { if ( - stringIsNotUndefinedNullAndEmpty(response.username) && - stringIsNotUndefinedNullAndEmpty(response.password) + stringIsNotUndefinedNullAndEmpty(vaultData.username) && + stringIsNotUndefinedNullAndEmpty(vaultData.password) ) { - this.doAutotype( - response.username, - response.password, - this.autotypeKeyboardShortcut.getArrayFormat(), - ); + this.doAutotype(vaultData, this.autotypeKeyboardShortcut.getArrayFormat()); } }); } @@ -89,8 +84,9 @@ export class MainDesktopAutotypeService { : this.logService.info("Enabling autotype failed."); } - private doAutotype(username: string, password: string, keyboardShortcut: string[]) { - const inputPattern = username + "\t" + password; + private doAutotype(vaultData: AutotypeVaultData, keyboardShortcut: string[]) { + const TAB = "\t"; + const inputPattern = vaultData.username + TAB + vaultData.password; const inputArray = new Array(inputPattern.length); for (let i = 0; i < inputPattern.length; i++) { diff --git a/apps/desktop/src/autofill/models/autotype-vault-data.ts b/apps/desktop/src/autofill/models/autotype-vault-data.ts new file mode 100644 index 00000000000..ee3db98c334 --- /dev/null +++ b/apps/desktop/src/autofill/models/autotype-vault-data.ts @@ -0,0 +1,8 @@ +/** + * Vault data used in autotype operations. + * `username` and `password` are guaranteed to be not null/undefined. + */ +export interface AutotypeVaultData { + username: string; + password: string; +} diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index fcb2f646743..22b5cdf9463 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -5,6 +5,8 @@ import type { autofill } from "@bitwarden/desktop-napi"; import { Command } from "../platform/main/autofill/command"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; +import { AutotypeVaultData } from "./models/autotype-vault-data"; + export default { runCommand: (params: RunCommandParams): Promise> => ipcRenderer.invoke("autofill.runCommand", params), @@ -133,10 +135,7 @@ export default { listenAutotypeRequest: ( fn: ( windowTitle: string, - completeCallback: ( - error: Error | null, - response: { username?: string; password?: string }, - ) => void, + completeCallback: (error: Error | null, response: AutotypeVaultData | null) => void, ) => void, ) => { ipcRenderer.on( @@ -149,7 +148,7 @@ export default { ) => { const { windowTitle } = data; - fn(windowTitle, (error, response) => { + fn(windowTitle, (error, vaultData) => { if (error) { ipcRenderer.send("autofill.completeError", { windowTitle, @@ -157,11 +156,9 @@ export default { }); return; } - - ipcRenderer.send("autofill.completeAutotypeRequest", { - windowTitle, - response, - }); + if (vaultData !== null) { + ipcRenderer.send("autofill.completeAutotypeRequest", vaultData); + } }); }, ); diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts new file mode 100644 index 00000000000..30cc800dd28 --- /dev/null +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.spec.ts @@ -0,0 +1,50 @@ +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { getAutotypeVaultData } from "./desktop-autotype.service"; + +describe("getAutotypeVaultData", () => { + it("should return vault data when cipher has username and password", () => { + const cipherView = new CipherView(); + cipherView.login.username = "foo"; + cipherView.login.password = "bar"; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(error).toBeNull(); + expect(vaultData?.username).toEqual("foo"); + expect(vaultData?.password).toEqual("bar"); + }); + + it("should return error when firstCipher is undefined", () => { + const cipherView = undefined; + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("No matching vault item."); + }); + + it("should return error when username is undefined", () => { + const cipherView = new CipherView(); + cipherView.login.username = undefined; + cipherView.login.password = "bar"; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("Vault item is undefined."); + }); + + it("should return error when password is undefined", () => { + const cipherView = new CipherView(); + cipherView.login.username = "foo"; + cipherView.login.password = undefined; + + const [error, vaultData] = getAutotypeVaultData(cipherView); + + expect(vaultData).toBeNull(); + expect(error).toBeDefined(); + expect(error?.message).toEqual("Vault item is undefined."); + }); +}); diff --git a/apps/desktop/src/autofill/services/desktop-autotype.service.ts b/apps/desktop/src/autofill/services/desktop-autotype.service.ts index 24ec3907a62..7ee889e7b81 100644 --- a/apps/desktop/src/autofill/services/desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autotype.service.ts @@ -17,6 +17,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { UserId } from "@bitwarden/user-core"; +import { AutotypeVaultData } from "../models/autotype-vault-data"; + import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service"; export const defaultWindowsAutotypeKeyboardShortcut: string[] = ["Control", "Shift", "B"]; @@ -27,6 +29,8 @@ export const AUTOTYPE_ENABLED = new KeyDefinition( { deserializer: (b) => b }, ); +export type Result = [E, null] | [null, T]; + /* Valid windows shortcut keys: Control, Alt, Super, Shift, letters A - Z Valid macOS shortcut keys: Control, Alt, Command, Shift, letters A - Z @@ -63,11 +67,8 @@ export class DesktopAutotypeService { ipc.autofill.listenAutotypeRequest(async (windowTitle, callback) => { const possibleCiphers = await this.matchCiphersToWindowTitle(windowTitle); const firstCipher = possibleCiphers?.at(0); - - return callback(null, { - username: firstCipher?.login?.username, - password: firstCipher?.login?.password, - }); + const [error, vaultData] = getAutotypeVaultData(firstCipher); + callback(error, vaultData); }); } @@ -176,3 +177,23 @@ export class DesktopAutotypeService { return possibleCiphers; } } + +/** + * @return an `AutotypeVaultData` object or an `Error` if the + * cipher or vault data within are undefined. + */ +export function getAutotypeVaultData( + cipherView: CipherView | undefined, +): Result { + if (!cipherView) { + return [Error("No matching vault item."), null]; + } else if (cipherView.login.username === undefined || cipherView.login.password === undefined) { + return [Error("Vault item is undefined."), null]; + } else { + const vaultData: AutotypeVaultData = { + username: cipherView.login.username, + password: cipherView.login.password, + }; + return [null, vaultData]; + } +} From a71ab020c4f4ed6825d1983c44aadc5b02dacaff Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 4 Nov 2025 12:47:26 -0500 Subject: [PATCH 008/249] PM-27807 remove unused es-lint disable (#17212) --- apps/browser/src/autofill/notification/bar.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/browser/src/autofill/notification/bar.html b/apps/browser/src/autofill/notification/bar.html index c0b57de612e..8934fe6a031 100644 --- a/apps/browser/src/autofill/notification/bar.html +++ b/apps/browser/src/autofill/notification/bar.html @@ -1,5 +1,4 @@ - - + Bitwarden From cef503ee9a0de7a81d5c15cef527effd4e43df19 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:13:10 +0000 Subject: [PATCH 009/249] [deps] SM: Update jest-diff to v30 (#15293) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> --- package-lock.json | 160 ++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 76 insertions(+), 86 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9636184c5ee..e456e257ca4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,7 +152,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.7", - "jest-diff": "29.7.0", + "jest-diff": "30.2.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.6.1", @@ -26136,26 +26136,51 @@ "license": "MIT" }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-diff/node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/@jest/schemas": { + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-diff/node_modules/@sinclair/typebox": { + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", + "license": "MIT" + }, "node_modules/jest-diff/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -26165,25 +26190,23 @@ } }, "node_modules/jest-diff/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-diff/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, "license": "MIT" }, "node_modules/jest-docblock": { @@ -26658,6 +26681,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-matcher-utils/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -27225,6 +27264,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/jest-snapshot/node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -32171,36 +32226,6 @@ } } }, - "node_modules/nx/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.34.0" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/nx/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", - "license": "MIT" - }, - "node_modules/nx/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/nx/node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -32258,21 +32283,6 @@ "node": ">=8" } }, - "node_modules/nx/node_modules/jest-diff": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", - "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "pretty-format": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/nx/node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -32318,26 +32328,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/nx/node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "30.0.5", - "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, - "node_modules/nx/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, "node_modules/nx/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", diff --git a/package.json b/package.json index c1becca3a31..e224fd00213 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.7", - "jest-diff": "29.7.0", + "jest-diff": "30.2.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.6.1", From f556f3b00043da081328929ee987370c5a79642b Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:22:57 -0500 Subject: [PATCH 010/249] Adding the removal from the access intelligence routing canActivate (#17216) --- .../access-intelligence/access-intelligence-routing.module.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts index 2e3c53d8d9f..4a37bea8872 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/access-intelligence-routing.module.ts @@ -9,9 +9,7 @@ const routes: Routes = [ { path: "", pathMatch: "full", redirectTo: "risk-insights" }, { path: "risk-insights", - canActivate: [ - organizationPermissionsGuard((org) => org.useRiskInsights && org.canAccessReports), - ], + canActivate: [organizationPermissionsGuard((org) => org.canAccessReports)], component: RiskInsightsComponent, data: { titleId: "RiskInsights", From 409dbc4c449c2bdf6ce8813465cdaf718cf2a12d Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:41:00 -0800 Subject: [PATCH 011/249] fix(sso-login): [PM-27674] (Auth) Make 'enter' press start sso process if ssoRequired (#17186) If user's email is NOT in the ssoRequiredCache, pressing "enter" takes them to the MP login screen. If the user's email is in the ssoRequiredCache, pressing "enter" starts the SSO login process. Feature Flags enabled: pm-22110-disable-alternate-login-methods --- libs/auth/src/angular/login/login.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index 4e1689b1054..9faa582c071 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -21,7 +21,7 @@ bitInput appAutofocus (input)="onEmailInput($event)" - (keyup.enter)="continuePressed()" + (keyup.enter)="ssoRequired ? handleSsoClick() : continuePressed()" />
From 92118e525d1b76d7cc646de90aaf37ca6804446f Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 4 Nov 2025 13:56:01 -0500 Subject: [PATCH 012/249] [PM-26984] Use medium instead of semibold or bold (#17185) --- .../src/autofill/content/components/buttons/action-button.ts | 2 +- .../content/components/notification/confirmation/message.ts | 2 +- .../content/components/notification/header-message.ts | 2 +- .../content/components/option-selection/option-items.ts | 2 +- .../src/autofill/content/components/rows/action-row.ts | 2 +- .../src/autofill/overlay/inline-menu/pages/list/list.scss | 4 ++-- .../src/autofill/components/autotype-shortcut.component.html | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/autofill/content/components/buttons/action-button.ts b/apps/browser/src/autofill/content/components/buttons/action-button.ts index b43bed7f96b..73fc1e79ec5 100644 --- a/apps/browser/src/autofill/content/components/buttons/action-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/action-button.ts @@ -68,7 +68,7 @@ const actionButtonStyles = ({ overflow: hidden; text-align: center; text-overflow: ellipsis; - font-weight: 700; + font-weight: 500; ${disabled || isLoading ? ` diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 01a2b783eda..36ea9c1f9d6 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -115,7 +115,7 @@ const notificationConfirmationButtonTextStyles = (theme: Theme) => css` ${baseTextStyles} color: ${themes[theme].primary[600]}; - font-weight: 700; + font-weight: 500; cursor: pointer; `; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts index 4b6e4722a83..2e51d82dd07 100644 --- a/apps/browser/src/autofill/content/components/notification/header-message.ts +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -21,5 +21,5 @@ const notificationHeaderMessageStyles = (theme: Theme) => css` color: ${themes[theme].text.main}; font-family: Inter, sans-serif; font-size: 18px; - font-weight: 600; + font-weight: 500; `; diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts index ceb72905357..58216b6c1b2 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-items.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts @@ -94,7 +94,7 @@ const optionsLabelStyles = ({ theme }: { theme: Theme }) => css` user-select: none; padding: 0.375rem ${spacing["3"]}; color: ${themes[theme].text.muted}; - font-weight: 600; + font-weight: 500; `; export const optionsMenuItemMaxWidth = 260; diff --git a/apps/browser/src/autofill/content/components/rows/action-row.ts b/apps/browser/src/autofill/content/components/rows/action-row.ts index 0380f91012a..8f13b166156 100644 --- a/apps/browser/src/autofill/content/components/rows/action-row.ts +++ b/apps/browser/src/autofill/content/components/rows/action-row.ts @@ -34,7 +34,7 @@ const actionRowStyles = (theme: Theme) => css` min-height: 40px; text-align: left; color: ${themes[theme].primary["600"]}; - font-weight: 700; + font-weight: 500; > span { display: block; diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss index 93f5f647ffe..ee9c68ee603 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/list.scss @@ -82,7 +82,7 @@ body * { width: 100%; font-family: $font-family-sans-serif; font-size: 1.6rem; - font-weight: 700; + font-weight: 500; text-align: left; background: transparent; border: none; @@ -187,7 +187,7 @@ body * { top: 0; z-index: 1; font-family: $font-family-sans-serif; - font-weight: 600; + font-weight: 500; font-size: 1rem; line-height: 1.3; letter-spacing: 0.025rem; diff --git a/apps/desktop/src/autofill/components/autotype-shortcut.component.html b/apps/desktop/src/autofill/components/autotype-shortcut.component.html index 774c299e0b6..6f73d4006ac 100644 --- a/apps/desktop/src/autofill/components/autotype-shortcut.component.html +++ b/apps/desktop/src/autofill/components/autotype-shortcut.component.html @@ -1,6 +1,6 @@ -
+
{{ "typeShortcut" | i18n }}
From d364dfdda07dbba6561a0ee446d938fe2ea78c52 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 4 Nov 2025 10:59:00 -0800 Subject: [PATCH 013/249] [PM-26182] - [Defect] [Browser] Safari - Autofill on page load default setting is missing yes or no (#16605) * handle parenthesis translation * add whitespace around placeholder with parentheses * fix test * fix label * fix spec --- apps/browser/src/_locales/en/messages.json | 10 ++++++++++ .../autofill-options.component.spec.ts | 4 ++-- .../autofill-options/autofill-options.component.ts | 5 ++++- .../autofill-options/uri-option.component.spec.ts | 6 +++--- .../autofill-options/uri-option.component.ts | 2 +- 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a7fe29e85d4..a8743b0db68 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4974,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts index 3aeeac6ca92..f1bb1ef942b 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.spec.ts @@ -201,12 +201,12 @@ describe("AutofillOptionsComponent", () => { it("updates the default autofill on page load label", () => { fixture.detectChanges(); - expect(component["autofillOptions"][0].label).toEqual("defaultLabel no"); + expect(component["autofillOptions"][0].label).toEqual("defaultLabelWithValue no"); (autofillSettingsService.autofillOnPageLoadDefault$ as BehaviorSubject).next(true); fixture.detectChanges(); - expect(component["autofillOptions"][0].label).toEqual("defaultLabel yes"); + expect(component["autofillOptions"][0].label).toEqual("defaultLabelWithValue yes"); }); it("hides the autofill on page load field when the setting is disabled", () => { diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts index e6b8b5c9aca..7215b1d6c67 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.ts @@ -218,7 +218,10 @@ export class AutofillOptionsComponent implements OnInit { return; } - this.autofillOptions[0].label = this.i18nService.t("defaultLabel", defaultOption.label); + this.autofillOptions[0].label = this.i18nService.t( + "defaultLabelWithValue", + defaultOption.label, + ); // Trigger change detection to update the label in the template this.autofillOptions = [...this.autofillOptions]; }); diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts index 2d06f5dcc29..ed70b4381d2 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.spec.ts @@ -77,19 +77,19 @@ describe("UriOptionComponent", () => { component.defaultMatchDetection = UriMatchStrategy.Domain; fixture.detectChanges(); - expect(component["uriMatchOptions"][0].label).toBe("defaultLabel baseDomain"); + expect(component["uriMatchOptions"][0].label).toBe("defaultLabelWithValue baseDomain"); }); it("should update the default uri match strategy label", () => { component.defaultMatchDetection = UriMatchStrategy.Exact; fixture.detectChanges(); - expect(component["uriMatchOptions"][0].label).toBe("defaultLabel exact"); + expect(component["uriMatchOptions"][0].label).toBe("defaultLabelWithValue exact"); component.defaultMatchDetection = UriMatchStrategy.StartsWith; fixture.detectChanges(); - expect(component["uriMatchOptions"][0].label).toBe("defaultLabel startsWith"); + expect(component["uriMatchOptions"][0].label).toBe("defaultLabelWithValue startsWith"); }); it("should focus the uri input when focusInput is called", () => { diff --git a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts index b61109a45bb..34ac284c3f3 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/uri-option.component.ts @@ -124,7 +124,7 @@ export class UriOptionComponent implements ControlValueAccessor { } this.uriMatchOptions[0].label = this.i18nService.t( - "defaultLabel", + "defaultLabelWithValue", this.uriMatchOptions.find((o) => o.value === value)?.label, ); } From 7e5f02f90c7bdc5a575de471d7c63601ed326750 Mon Sep 17 00:00:00 2001 From: Shane Melton Date: Tue, 4 Nov 2025 12:15:53 -0800 Subject: [PATCH 014/249] [PM-24469] Implement Risk Insights for Premium in Cipher view component (#17012) * [PM-24469] Refactor CipherViewComponent to use Angular signals and computed properties for improved reactivity * [PM-24469] Refactor CipherViewComponent to utilize Angular signals for organization data retrieval * [PM-24469] Refactor CipherViewComponent to utilize Angular signals for folder data retrieval * [PM-24469] Cleanup organization signal * [PM-24469] Refactor CipherViewComponent to replace signal for card expiration with computed property * [PM-24469] Improve collections loading in CipherViewComponent * [PM-24469] Remove redundant loadCipherData method * [PM-24469] Refactor CipherViewComponent to replace signal with computed property for pending change password tasks * [PM-24469] Refactor LoginCredentialsViewComponent to rename hadPendingChangePasswordTask to showChangePasswordLink for clarity * [PM-24469] Introduce showChangePasswordLink computed property for improved readability * [PM-24469] Initial RI for premium logic * [PM-24469] Refactor checkPassword risk checking logic * [PM-24469] Cleanup premium check * [PM-24469] Cleanup UI visuals * [PM-24469] Fix missing typography import * [PM-24469] Cleanup docs * [PM-24469] Add feature flag * [PM-24469] Ensure password risk check is only performed when the feature is enabled, and the cipher is editable by the user, and it has a password * [PM-24469] Refactor password risk evaluation logic and add unit tests for risk assessment * [PM-24469] Fix mismatched CipherId type * [PM-24469] Fix test dependencies * [PM-24469] Fix config service mock in emergency view dialog spec * [PM-24469] Wait for decrypted vault before calculating cipher risk * [PM-24469] startWith(false) for passwordIsAtRisk signal to avoid showing stale values when cipher changes * [PM-24469] Exclude organization owned ciphers from JIT risk analysis * [PM-24469] Add initial cipher-view component test boilerplate * [PM-24469] Add passwordIsAtRisk signal tests * [PM-24469] Ignore soft deleted items for RI for premium feature * [PM-24469] Fix tests --- apps/desktop/src/locales/en/messages.json | 3 + .../emergency-view-dialog.component.spec.ts | 10 +- apps/web/src/locales/en/messages.json | 3 + .../src/services/jslib-services.module.ts | 7 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../abstractions/cipher-risk.service.spec.ts | 88 +++++ .../vault/abstractions/cipher-risk.service.ts | 24 +- .../src/vault/models/view/cipher.view.ts | 6 + .../default-cipher-risk.service.spec.ts | 63 +++- .../services/default-cipher-risk.service.ts | 11 +- .../cipher-view/cipher-view.component.html | 62 ++-- .../cipher-view/cipher-view.component.spec.ts | 287 +++++++++++++++ .../src/cipher-view/cipher-view.component.ts | 332 +++++++++++------- .../login-credentials-view.component.html | 12 +- .../login-credentials-view.component.ts | 2 +- 15 files changed, 732 insertions(+), 180 deletions(-) create mode 100644 libs/common/src/vault/abstractions/cipher-risk.service.spec.ts create mode 100644 libs/vault/src/cipher-view/cipher-view.component.spec.ts diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index e2032bf27b1..da8d9ea0e34 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -69,6 +69,9 @@ } } }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "welcomeBack": { "message": "Welcome back" }, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 60993924ded..d13987f2e8b 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -8,6 +8,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -16,6 +17,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId, EmergencyAccessId } from "@bitwarden/common/types/guid"; +import { CipherRiskService } from "@bitwarden/common/vault/abstractions/cipher-risk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -68,6 +70,12 @@ describe("EmergencyViewDialogComponent", () => { useValue: { environment$: of({ getIconsUrl: () => "https://icons.example.com" }) }, }, { provide: DomainSettingsService, useValue: { showFavicons$: of(true) } }, + { provide: CipherRiskService, useValue: mock() }, + { + provide: BillingAccountProfileStateService, + useValue: mock(), + }, + { provide: ConfigService, useValue: mock() }, ], }) .overrideComponent(EmergencyViewDialogComponent, { @@ -78,7 +86,6 @@ describe("EmergencyViewDialogComponent", () => { provide: ChangeLoginPasswordService, useValue: ChangeLoginPasswordService, }, - { provide: ConfigService, useValue: ConfigService }, { provide: CipherService, useValue: mock() }, ], }, @@ -89,7 +96,6 @@ describe("EmergencyViewDialogComponent", () => { provide: ChangeLoginPasswordService, useValue: mock(), }, - { provide: ConfigService, useValue: mock() }, { provide: CipherService, useValue: mock() }, ], }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e91464cb174..0a0152c5965 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -23,6 +23,9 @@ "passwordRisk": { "message": "Password Risk" }, + "noEditPermissions": { + "message": "You don't have permission to edit this item" + }, "reviewAtRiskPasswords": { "message": "Review at-risk passwords (weak, exposed, or reused) across applications. Select your most critical applications to prioritize security actions for your users to address at-risk passwords." }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 38ce3c0fcc2..c60bc2e2f0b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -282,6 +282,7 @@ import { } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; +import { CipherRiskService } from "@bitwarden/common/vault/abstractions/cipher-risk.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -303,6 +304,7 @@ import { import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service"; import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; +import { DefaultCipherRiskService } from "@bitwarden/common/vault/services/default-cipher-risk.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -605,6 +607,11 @@ const safeProviders: SafeProvider[] = [ MessagingServiceAbstraction, ], }), + safeProvider({ + provide: CipherRiskService, + useClass: DefaultCipherRiskService, + deps: [SdkService, CipherServiceAbstraction], + }), safeProvider({ provide: InternalFolderService, useClass: FolderService, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index d9effd21b30..d14ad8a64f3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -58,6 +58,7 @@ export enum FeatureFlag { PM22136_SdkCipherEncryption = "pm-22136-sdk-cipher-encryption", CipherKeyEncryption = "cipher-key-encryption", AutofillConfirmation = "pm-25083-autofill-confirm-from-search", + RiskInsightsForPremium = "pm-23904-risk-insights-for-premium", /* Platform */ IpcChannelFramework = "ipc-channel-framework", @@ -106,6 +107,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM22134SdkCipherListView]: FALSE, [FeatureFlag.PM22136_SdkCipherEncryption]: FALSE, [FeatureFlag.AutofillConfirmation]: FALSE, + [FeatureFlag.RiskInsightsForPremium]: FALSE, /* Auth */ [FeatureFlag.PM22110_DisableAlternateLoginMethods]: FALSE, diff --git a/libs/common/src/vault/abstractions/cipher-risk.service.spec.ts b/libs/common/src/vault/abstractions/cipher-risk.service.spec.ts new file mode 100644 index 00000000000..2c87191cd96 --- /dev/null +++ b/libs/common/src/vault/abstractions/cipher-risk.service.spec.ts @@ -0,0 +1,88 @@ +import type { CipherRiskResult, CipherId } from "@bitwarden/sdk-internal"; + +import { isPasswordAtRisk } from "./cipher-risk.service"; + +describe("isPasswordAtRisk", () => { + const mockId = "00000000-0000-0000-0000-000000000000" as unknown as CipherId; + + const createRisk = (overrides: Partial = {}): CipherRiskResult => ({ + id: mockId, + password_strength: 4, + exposed_result: { type: "NotChecked" }, + reuse_count: 1, + ...overrides, + }); + + describe("exposed password risk", () => { + it.each([ + { value: 5, expected: true, desc: "found with value > 0" }, + { value: 0, expected: false, desc: "found but value is 0" }, + ])("should return $expected when password is $desc", ({ value, expected }) => { + const risk = createRisk({ exposed_result: { type: "Found", value } }); + expect(isPasswordAtRisk(risk)).toBe(expected); + }); + + it("should return false when password is not checked", () => { + expect(isPasswordAtRisk(createRisk())).toBe(false); + }); + }); + + describe("password reuse risk", () => { + it.each([ + { count: 2, expected: true, desc: "reused (reuse_count > 1)" }, + { count: 1, expected: false, desc: "not reused" }, + { count: undefined, expected: false, desc: "undefined" }, + ])("should return $expected when reuse_count is $desc", ({ count, expected }) => { + const risk = createRisk({ reuse_count: count }); + expect(isPasswordAtRisk(risk)).toBe(expected); + }); + }); + + describe("password strength risk", () => { + it.each([ + { strength: 0, expected: true }, + { strength: 1, expected: true }, + { strength: 2, expected: true }, + { strength: 3, expected: false }, + { strength: 4, expected: false }, + ])("should return $expected when password strength is $strength", ({ strength, expected }) => { + const risk = createRisk({ password_strength: strength }); + expect(isPasswordAtRisk(risk)).toBe(expected); + }); + }); + + describe("multiple risk factors", () => { + it.each<{ desc: string; overrides: Partial; expected: boolean }>([ + { + desc: "exposed and reused", + overrides: { + exposed_result: { type: "Found" as const, value: 3 }, + reuse_count: 2, + }, + expected: true, + }, + { + desc: "reused and weak strength", + overrides: { password_strength: 2, reuse_count: 2 }, + expected: true, + }, + { + desc: "all three risk factors", + overrides: { + password_strength: 1, + exposed_result: { type: "Found" as const, value: 10 }, + reuse_count: 3, + }, + expected: true, + }, + { + desc: "no risk factors", + overrides: { reuse_count: undefined }, + expected: false, + }, + ])("should return $expected when $desc present", ({ overrides, expected }) => { + const risk = createRisk(overrides); + expect(isPasswordAtRisk(risk)).toBe(expected); + }); + }); +}); diff --git a/libs/common/src/vault/abstractions/cipher-risk.service.ts b/libs/common/src/vault/abstractions/cipher-risk.service.ts index 6bbd9d7791e..78f1c50da19 100644 --- a/libs/common/src/vault/abstractions/cipher-risk.service.ts +++ b/libs/common/src/vault/abstractions/cipher-risk.service.ts @@ -1,12 +1,10 @@ import type { CipherRiskResult, CipherRiskOptions, - ExposedPasswordResult, PasswordReuseMap, - CipherId, } from "@bitwarden/sdk-internal"; -import { UserId } from "../../types/guid"; +import { UserId, CipherId } from "../../types/guid"; import { CipherView } from "../models/view/cipher.view"; export abstract class CipherRiskService { @@ -51,5 +49,21 @@ export abstract class CipherRiskService { abstract buildPasswordReuseMap(ciphers: CipherView[], userId: UserId): Promise; } -// Re-export SDK types for convenience -export type { CipherRiskResult, CipherRiskOptions, ExposedPasswordResult, PasswordReuseMap }; +/** + * Evaluates if a password represented by a CipherRiskResult is considered at risk. + * + * A password is considered at risk if any of the following conditions are true: + * - The password has been exposed in data breaches + * - The password is reused across multiple ciphers + * - The password has weak strength (password_strength < 3) + * + * @param risk - The CipherRiskResult to evaluate + * @returns true if the password is at risk, false otherwise + */ +export function isPasswordAtRisk(risk: CipherRiskResult): boolean { + return ( + (risk.exposed_result.type === "Found" && risk.exposed_result.value > 0) || + (risk.reuse_count ?? 1) > 1 || + risk.password_strength < 3 + ); +} diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index 7412c68d695..0d4ab8e5207 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -113,6 +113,12 @@ export class CipherView implements View, InitializerMetadata { return this.passwordHistory && this.passwordHistory.length > 0; } + get hasLoginPassword(): boolean { + return ( + this.type === CipherType.Login && this.login?.password != null && this.login.password !== "" + ); + } + get hasAttachments(): boolean { return !!this.attachments && this.attachments.length > 0; } diff --git a/libs/common/src/vault/services/default-cipher-risk.service.spec.ts b/libs/common/src/vault/services/default-cipher-risk.service.spec.ts index afd52bde6cf..e5231241462 100644 --- a/libs/common/src/vault/services/default-cipher-risk.service.spec.ts +++ b/libs/common/src/vault/services/default-cipher-risk.service.spec.ts @@ -1,11 +1,11 @@ import { mock } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, Observable } from "rxjs"; -import type { CipherRiskOptions, CipherId, CipherRiskResult } from "@bitwarden/sdk-internal"; +import type { CipherRiskOptions, CipherRiskResult } from "@bitwarden/sdk-internal"; import { asUuid } from "../../platform/abstractions/sdk/sdk.service"; import { MockSdkService } from "../../platform/spec/mock-sdk.service"; -import { UserId } from "../../types/guid"; +import { UserId, CipherId } from "../../types/guid"; import { CipherService } from "../abstractions/cipher.service"; import { CipherType } from "../enums/cipher-type"; import { CipherView } from "../models/view/cipher.view"; @@ -19,9 +19,9 @@ describe("DefaultCipherRiskService", () => { let mockCipherService: jest.Mocked; const mockUserId = "test-user-id" as UserId; - const mockCipherId1 = "cbea34a8-bde4-46ad-9d19-b05001228ab2"; - const mockCipherId2 = "cbea34a8-bde4-46ad-9d19-b05001228ab3"; - const mockCipherId3 = "cbea34a8-bde4-46ad-9d19-b05001228ab4"; + const mockCipherId1 = "cbea34a8-bde4-46ad-9d19-b05001228ab2" as CipherId; + const mockCipherId2 = "cbea34a8-bde4-46ad-9d19-b05001228ab3" as CipherId; + const mockCipherId3 = "cbea34a8-bde4-46ad-9d19-b05001228ab4" as CipherId; beforeEach(() => { sdkService = new MockSdkService(); @@ -534,5 +534,56 @@ describe("DefaultCipherRiskService", () => { // Verify password_reuse_map was called twice (fresh computation each time) expect(mockCipherRiskClient.password_reuse_map).toHaveBeenCalledTimes(2); }); + + it("should wait for a decrypted vault before computing risk", async () => { + const mockClient = sdkService.simulate.userLogin(mockUserId); + const mockCipherRiskClient = mockClient.vault.mockDeep().cipher_risk.mockDeep(); + + const cipher = new CipherView(); + cipher.id = mockCipherId1; + cipher.type = CipherType.Login; + cipher.login = new LoginView(); + cipher.login.password = "password1"; + + // Simulate the observable emitting null (undecrypted vault) first, then the decrypted ciphers + const cipherViewsSubject = new BehaviorSubject(null); + mockCipherService.cipherViews$.mockReturnValue( + cipherViewsSubject as Observable, + ); + + mockCipherRiskClient.password_reuse_map.mockReturnValue({}); + mockCipherRiskClient.compute_risk.mockResolvedValue([ + { + id: mockCipherId1 as any, + password_strength: 4, + exposed_result: { type: "NotChecked" }, + reuse_count: 1, + }, + ]); + + // Initiate the async call but don't await yet + const computePromise = cipherRiskService.computeCipherRiskForUser( + asUuid(mockCipherId1), + mockUserId, + true, + ); + + // Simulate a tick to allow the service to process the null emission + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Now emit the actual decrypted ciphers + cipherViewsSubject.next([cipher]); + + const result = await computePromise; + + expect(mockCipherRiskClient.compute_risk).toHaveBeenCalledWith( + [expect.objectContaining({ password: "password1" })], + { + passwordMap: expect.any(Object), + checkExposed: true, + }, + ); + expect(result).toEqual(expect.objectContaining({ id: expect.anything() })); + }); }); }); diff --git a/libs/common/src/vault/services/default-cipher-risk.service.ts b/libs/common/src/vault/services/default-cipher-risk.service.ts index d9f0243edfe..4b4558e5e7a 100644 --- a/libs/common/src/vault/services/default-cipher-risk.service.ts +++ b/libs/common/src/vault/services/default-cipher-risk.service.ts @@ -1,16 +1,17 @@ import { firstValueFrom, switchMap } from "rxjs"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { filterOutNullish } from "@bitwarden/common/vault/utils/observable-utilities"; import { CipherLoginDetails, CipherRiskOptions, PasswordReuseMap, - CipherId, CipherRiskResult, + CipherId as SdkCipherId, } from "@bitwarden/sdk-internal"; import { SdkService, asUuid } from "../../platform/abstractions/sdk/sdk.service"; -import { UserId } from "../../types/guid"; +import { UserId, CipherId } from "../../types/guid"; import { CipherRiskService as CipherRiskServiceAbstraction } from "../abstractions/cipher-risk.service"; import { CipherType } from "../enums/cipher-type"; import { CipherView } from "../models/view/cipher.view"; @@ -52,7 +53,9 @@ export class DefaultCipherRiskService implements CipherRiskServiceAbstraction { checkExposed: boolean = true, ): Promise { // Get all ciphers for the user - const allCiphers = await firstValueFrom(this.cipherService.cipherViews$(userId)); + const allCiphers = await firstValueFrom( + this.cipherService.cipherViews$(userId).pipe(filterOutNullish()), + ); // Find the specific cipher const targetCipher = allCiphers?.find((c) => asUuid(c.id) === cipherId); @@ -106,7 +109,7 @@ export class DefaultCipherRiskService implements CipherRiskServiceAbstraction { .map( (cipher) => ({ - id: asUuid(cipher.id), + id: asUuid(cipher.id), password: cipher.login.password!, username: cipher.login.username, }) satisfies CipherLoginDetails, diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html index b523c11c7e3..3d0cc4c4414 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.html +++ b/libs/vault/src/cipher-view/cipher-view.component.html @@ -1,89 +1,85 @@ - - + + {{ "cardExpiredMessage" | i18n }} {{ "changeAtRiskPasswordAndAddWebsite" | i18n }} - - + + {{ "changeAtRiskPassword" | i18n }} -

+

{{ "noEditPermissions" | i18n }}

- + - + - + - - + + - - + + - + - + diff --git a/libs/vault/src/cipher-view/cipher-view.component.spec.ts b/libs/vault/src/cipher-view/cipher-view.component.spec.ts new file mode 100644 index 00000000000..18a5132781b --- /dev/null +++ b/libs/vault/src/cipher-view/cipher-view.component.spec.ts @@ -0,0 +1,287 @@ +import { NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +// eslint-disable-next-line no-restricted-imports +import { CollectionService } from "@bitwarden/admin-console/common"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherRiskService } from "@bitwarden/common/vault/abstractions/cipher-risk.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; +import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { TaskService } from "@bitwarden/common/vault/tasks"; + +import { ChangeLoginPasswordService } from "../abstractions/change-login-password.service"; + +import { CipherViewComponent } from "./cipher-view.component"; + +describe("CipherViewComponent", () => { + let component: CipherViewComponent; + let fixture: ComponentFixture; + + // Mock services + let mockAccountService: AccountService; + let mockOrganizationService: OrganizationService; + let mockCollectionService: CollectionService; + let mockFolderService: FolderService; + let mockTaskService: TaskService; + let mockPlatformUtilsService: PlatformUtilsService; + let mockChangeLoginPasswordService: ChangeLoginPasswordService; + let mockCipherService: CipherService; + let mockViewPasswordHistoryService: ViewPasswordHistoryService; + let mockI18nService: I18nService; + let mockLogService: LogService; + let mockCipherRiskService: CipherRiskService; + let mockBillingAccountProfileStateService: BillingAccountProfileStateService; + let mockConfigService: ConfigService; + + // Mock data + let mockCipherView: CipherView; + let featureFlagEnabled$: BehaviorSubject; + let hasPremiumFromAnySource$: BehaviorSubject; + let activeAccount$: BehaviorSubject; + + beforeEach(async () => { + // Setup mock observables + activeAccount$ = new BehaviorSubject({ + id: "test-user-id", + email: "test@example.com", + } as Account); + + featureFlagEnabled$ = new BehaviorSubject(false); + hasPremiumFromAnySource$ = new BehaviorSubject(true); + + // Create service mocks + mockAccountService = mock(); + mockAccountService.activeAccount$ = activeAccount$; + + mockOrganizationService = mock(); + mockCollectionService = mock(); + mockFolderService = mock(); + mockTaskService = mock(); + mockPlatformUtilsService = mock(); + mockChangeLoginPasswordService = mock(); + mockCipherService = mock(); + mockViewPasswordHistoryService = mock(); + mockI18nService = mock({ + t: (key: string) => key, + }); + mockLogService = mock(); + mockCipherRiskService = mock(); + + mockBillingAccountProfileStateService = mock(); + mockBillingAccountProfileStateService.hasPremiumFromAnySource$ = jest + .fn() + .mockReturnValue(hasPremiumFromAnySource$); + + mockConfigService = mock(); + mockConfigService.getFeatureFlag$ = jest.fn().mockReturnValue(featureFlagEnabled$); + + // Setup mock cipher view + mockCipherView = new CipherView(); + mockCipherView.id = "cipher-id"; + mockCipherView.name = "Test Cipher"; + + await TestBed.configureTestingModule({ + imports: [CipherViewComponent], + providers: [ + { provide: AccountService, useValue: mockAccountService }, + { provide: OrganizationService, useValue: mockOrganizationService }, + { provide: CollectionService, useValue: mockCollectionService }, + { provide: FolderService, useValue: mockFolderService }, + { provide: TaskService, useValue: mockTaskService }, + { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, + { provide: ChangeLoginPasswordService, useValue: mockChangeLoginPasswordService }, + { provide: CipherService, useValue: mockCipherService }, + { provide: ViewPasswordHistoryService, useValue: mockViewPasswordHistoryService }, + { provide: I18nService, useValue: mockI18nService }, + { provide: LogService, useValue: mockLogService }, + { provide: CipherRiskService, useValue: mockCipherRiskService }, + { + provide: BillingAccountProfileStateService, + useValue: mockBillingAccountProfileStateService, + }, + { provide: ConfigService, useValue: mockConfigService }, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + // Override the component template to avoid rendering child components + // Allows testing component logic without + // needing to provide dependencies for all child components. + .overrideComponent(CipherViewComponent, { + set: { + template: "
{{ passwordIsAtRisk() }}
", + imports: [], + }, + }) + .compileComponents(); + + fixture = TestBed.createComponent(CipherViewComponent); + component = fixture.componentInstance; + }); + + describe("passwordIsAtRisk signal", () => { + // Helper to create a cipher view with login credentials + const createLoginCipherView = (): CipherView => { + const cipher = new CipherView(); + cipher.id = "cipher-id"; + cipher.name = "Test Login"; + cipher.type = CipherType.Login; + cipher.edit = true; + cipher.organizationId = undefined; + // Set up login with password so hasLoginPassword returns true + cipher.login = { password: "test-password" } as any; + return cipher; + }; + + beforeEach(() => { + // Reset observables to default values for this test suite + featureFlagEnabled$.next(true); + hasPremiumFromAnySource$.next(true); + + // Setup default mock for computeCipherRiskForUser (individual tests can override) + mockCipherRiskService.computeCipherRiskForUser = jest.fn().mockResolvedValue({ + password_strength: 4, + exposed_result: { type: "NotFound" }, + reuse_count: 1, + }); + + // Recreate the fixture for each test in this suite. + // This ensures that the signal's observable subscribes with the correct + // initial state + fixture = TestBed.createComponent(CipherViewComponent); + component = fixture.componentInstance; + }); + + it("returns false when feature flag is disabled", fakeAsync(() => { + featureFlagEnabled$.next(false); + + const cipher = createLoginCipherView(); + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns false when cipher has no login password", fakeAsync(() => { + const cipher = createLoginCipherView(); + cipher.login = {} as any; // No password + + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns false when user does not have edit access", fakeAsync(() => { + const cipher = createLoginCipherView(); + cipher.edit = false; + + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns false when cipher is deleted", fakeAsync(() => { + const cipher = createLoginCipherView(); + cipher.deletedDate = new Date(); + + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns false for organization-owned ciphers", fakeAsync(() => { + const cipher = createLoginCipherView(); + cipher.organizationId = "org-id"; + + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns false when user is not premium", fakeAsync(() => { + hasPremiumFromAnySource$.next(false); + + const cipher = createLoginCipherView(); + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + tick(); + + expect(mockCipherRiskService.computeCipherRiskForUser).not.toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + + it("returns true when password is weak", fakeAsync(() => { + // Setup mock to return weak password + const mockRiskyResult = { + password_strength: 2, // Weak password (< 3) + exposed_result: { type: "NotFound" }, + reuse_count: 1, + }; + mockCipherRiskService.computeCipherRiskForUser = jest.fn().mockResolvedValue(mockRiskyResult); + + const cipher = createLoginCipherView(); + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + + // Initial value should be false (from startWith(false)) + expect(component.passwordIsAtRisk()).toBe(false); + + // Wait for async operations to complete + tick(); + fixture.detectChanges(); + + // After async completes, should reflect the weak password + expect(mockCipherRiskService.computeCipherRiskForUser).toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(true); + })); + + it("returns false when password is strong and not exposed", fakeAsync(() => { + // Setup mock to return safe password + const mockSafeResult = { + password_strength: 4, // Strong password + exposed_result: { type: "NotFound" }, // Not exposed + reuse_count: 1, // Not reused + }; + mockCipherRiskService.computeCipherRiskForUser = jest.fn().mockResolvedValue(mockSafeResult); + + const cipher = createLoginCipherView(); + fixture.componentRef.setInput("cipher", cipher); + fixture.detectChanges(); + + // Initial value should be false + expect(component.passwordIsAtRisk()).toBe(false); + + // Wait for async operations to complete + tick(); + fixture.detectChanges(); + + // Should remain false for safe password + expect(mockCipherRiskService.computeCipherRiskForUser).toHaveBeenCalled(); + expect(component.passwordIsAtRisk()).toBe(false); + })); + }); +}); diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 15cb7d4651f..043a5a63c49 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -1,30 +1,38 @@ import { CommonModule } from "@angular/common"; -import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; -import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; +import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core"; +import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { combineLatest, of, switchMap, map, catchError, from, Observable, startWith } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { isCardExpired } from "@bitwarden/common/autofill/utils"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { getByIds } from "@bitwarden/common/platform/misc"; import { CipherId, EmergencyAccessId, UserId } from "@bitwarden/common/types/guid"; +import { + CipherRiskService, + isPasswordAtRisk, +} from "@bitwarden/common/vault/abstractions/cipher-risk.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { SecurityTaskType, TaskService } from "@bitwarden/common/vault/tasks"; -import { AnchorLinkDirective, CalloutModule, SearchModule } from "@bitwarden/components"; +import { + CalloutModule, + SearchModule, + TypographyModule, + AnchorLinkDirective, +} from "@bitwarden/components"; import { ChangeLoginPasswordService } from "../abstractions/change-login-password.service"; @@ -39,11 +47,10 @@ import { LoginCredentialsViewComponent } from "./login-credentials/login-credent import { SshKeyViewComponent } from "./sshkey-sections/sshkey-view.component"; import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-identity-sections.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({ selector: "app-cipher-view", templateUrl: "cipher-view.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CalloutModule, CommonModule, @@ -60,38 +67,37 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide LoginCredentialsViewComponent, AutofillOptionsViewComponent, AnchorLinkDirective, + TypographyModule, ], }) -export class CipherViewComponent implements OnChanges, OnDestroy { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input({ required: true }) cipher: CipherView | null = null; +export class CipherViewComponent { + /** + * The cipher to display details for + */ + readonly cipher = input.required(); - // Required for fetching attachment data when viewed from cipher via emergency access - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() emergencyAccessId?: EmergencyAccessId; + /** + * Observable version of the cipher input + */ + private readonly cipher$ = toObservable(this.cipher); - activeUserId$ = getUserId(this.accountService.activeAccount$); + /** + * Required for fetching attachment data when viewed from cipher via emergency access + */ + readonly emergencyAccessId = input(); /** * Optional list of collections the cipher is assigned to. If none are provided, they will be fetched using the * `CipherService` and the `collectionIds` property of the cipher. */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() collections?: CollectionView[]; + readonly collections = input(undefined); - /** Should be set to true when the component is used within the Admin Console */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() isAdminConsole?: boolean = false; + /** + * Should be set to true when the component is used within the Admin Console + */ + readonly isAdminConsole = input(false); - organization$: Observable | undefined; - folder$: Observable | undefined; - private destroyed$: Subject = new Subject(); - cardIsExpired: boolean = false; - hadPendingChangePasswordTask: boolean = false; + readonly activeUserId$ = getUserId(this.accountService.activeAccount$); constructor( private organizationService: OrganizationService, @@ -103,126 +109,206 @@ export class CipherViewComponent implements OnChanges, OnDestroy { private changeLoginPasswordService: ChangeLoginPasswordService, private cipherService: CipherService, private logService: LogService, + private cipherRiskService: CipherRiskService, + private billingAccountService: BillingAccountProfileStateService, + private configService: ConfigService, ) {} - async ngOnChanges() { - if (this.cipher == null) { - return; - } + readonly resolvedCollections = toSignal( + combineLatest([this.activeUserId$, this.cipher$, toObservable(this.collections)]).pipe( + switchMap(([userId, cipher, providedCollections]) => { + // Use provided collections if available + if (providedCollections && providedCollections.length > 0) { + return of(providedCollections); + } + // Otherwise, load collections based on cipher's collectionIds + if (cipher.collectionIds && cipher.collectionIds.length > 0) { + return this.collectionService + .decryptedCollections$(userId) + .pipe(getByIds(cipher.collectionIds)); + } + return of(undefined); + }), + ), + ); - await this.loadCipherData(); + readonly organization = toSignal( + combineLatest([this.activeUserId$, this.cipher$]).pipe( + switchMap(([userId, cipher]) => { + if (!userId || !cipher?.organizationId) { + return of(undefined); + } + return this.organizationService.organizations$(userId).pipe( + map((organizations) => { + return organizations.find((org) => org.id === cipher.organizationId); + }), + ); + }), + ), + ); + readonly folder = toSignal( + combineLatest([this.activeUserId$, this.cipher$]).pipe( + switchMap(([userId, cipher]) => { + if (!userId || !cipher?.folderId) { + return of(undefined); + } + return this.folderService.getDecrypted$(cipher.folderId, userId); + }), + ), + ); - this.cardIsExpired = isCardExpired(this.cipher.card); - } + readonly hadPendingChangePasswordTask = toSignal( + combineLatest([this.activeUserId$, this.cipher$]).pipe( + switchMap(([userId, cipher]) => { + // Early exit if not a Login cipher owned by an organization + if (cipher?.type !== CipherType.Login || !cipher?.organizationId) { + return of(false); + } - ngOnDestroy(): void { - this.destroyed$.next(); - this.destroyed$.complete(); - } + return combineLatest([ + this.cipherService.ciphers$(userId), + this.defaultTaskService.pendingTasks$(userId), + ]).pipe( + map(([allCiphers, tasks]) => { + const cipherServiceCipher = allCiphers[cipher?.id as CipherId]; - get hasCard() { - if (!this.cipher) { + // Show tasks only for Manage and Edit permissions + if (!cipherServiceCipher?.edit || !cipherServiceCipher?.viewPassword) { + return false; + } + + return ( + tasks?.some( + (task) => + task.cipherId === cipher?.id && + task.type === SecurityTaskType.UpdateAtRiskCredential, + ) ?? false + ); + }), + catchError((error: unknown) => { + this.logService.error("Failed to retrieve change password tasks for cipher", error); + return of(false); + }), + ); + }), + ), + { initialValue: false }, + ); + + readonly hasCard = computed(() => { + const cipher = this.cipher(); + if (!cipher) { return false; } - const { cardholderName, code, expMonth, expYear, number } = this.cipher.card; + const { cardholderName, code, expMonth, expYear, number } = cipher.card; return cardholderName || code || expMonth || expYear || number; - } + }); - get hasLogin() { - if (!this.cipher) { + readonly cardIsExpired = computed(() => { + const cipher = this.cipher(); + if (cipher == null) { + return false; + } + return isCardExpired(cipher.card); + }); + + readonly hasLogin = computed(() => { + const cipher = this.cipher(); + if (!cipher) { return false; } - const { username, password, totp, fido2Credentials } = this.cipher.login; + const { username, password, totp, fido2Credentials } = cipher.login; return username || password || totp || fido2Credentials?.length > 0; - } + }); - get hasAutofill() { - const uris = this.cipher?.login?.uris.length ?? 0; + readonly hasAutofill = computed(() => { + const cipher = this.cipher(); + const uris = cipher?.login?.uris.length ?? 0; return uris > 0; - } + }); - get hasSshKey() { - return !!this.cipher?.sshKey?.privateKey; - } + readonly hasSshKey = computed(() => { + const cipher = this.cipher(); + return !!cipher?.sshKey?.privateKey; + }); - get hasLoginUri() { - return this.cipher?.login?.hasUris; - } + readonly hasLoginUri = computed(() => { + const cipher = this.cipher(); + return cipher?.login?.hasUris; + }); - async loadCipherData() { - if (!this.cipher) { - return; - } - - const userId = await firstValueFrom(this.activeUserId$); - - // Load collections if not provided and the cipher has collectionIds - if ( - this.cipher.collectionIds && - this.cipher.collectionIds.length > 0 && - (!this.collections || this.collections.length === 0) - ) { - this.collections = await firstValueFrom( - this.collectionService - .decryptedCollections$(userId) - .pipe(getByIds(this.cipher.collectionIds)), - ); - } - - if (this.cipher.organizationId) { - this.organization$ = this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(this.cipher.organizationId)) - .pipe(takeUntil(this.destroyed$)); - - if (this.cipher.type === CipherType.Login) { - await this.checkPendingChangePasswordTasks(userId); - } - } - - if (this.cipher.folderId) { - this.folder$ = this.folderService - .getDecrypted$(this.cipher.folderId, userId) - .pipe(takeUntil(this.destroyed$)); - } - } - - async checkPendingChangePasswordTasks(userId: UserId): Promise { - try { - // Show Tasks for Manage and Edit permissions - // Using cipherService to see if user has access to cipher in a non-AC context to address with Edit Except Password permissions - const allCiphers = await firstValueFrom(this.cipherService.ciphers$(userId)); - const cipherServiceCipher = allCiphers[this.cipher?.id as CipherId]; - - if (!cipherServiceCipher?.edit || !cipherServiceCipher?.viewPassword) { - this.hadPendingChangePasswordTask = false; - return; - } - - const tasks = await firstValueFrom(this.defaultTaskService.pendingTasks$(userId)); - - this.hadPendingChangePasswordTask = tasks?.some((task) => { - return ( - task.cipherId === this.cipher?.id && task.type === SecurityTaskType.UpdateAtRiskCredential + /** + * Whether the login password for the cipher is considered at risk. + * The password is only evaluated when the user is premium and has edit access to the cipher. + */ + readonly passwordIsAtRisk = toSignal( + combineLatest([ + this.activeUserId$, + this.cipher$, + this.configService.getFeatureFlag$(FeatureFlag.RiskInsightsForPremium), + ]).pipe( + switchMap(([userId, cipher, featureEnabled]) => { + if ( + !featureEnabled || + !cipher.hasLoginPassword || + !cipher.edit || + cipher.organizationId || + cipher.isDeleted + ) { + return of(false); + } + return this.switchPremium$( + userId, + () => + from(this.checkIfPasswordIsAtRisk(cipher.id as CipherId, userId as UserId)).pipe( + startWith(false), + ), + () => of(false), ); - }); - } catch (error) { - this.hadPendingChangePasswordTask = false; - this.logService.error("Failed to retrieve change password tasks for cipher", error); - } - } + }), + ), + { initialValue: false }, + ); + + readonly showChangePasswordLink = computed(() => { + return this.hasLoginUri() && (this.hadPendingChangePasswordTask() || this.passwordIsAtRisk()); + }); launchChangePassword = async () => { - if (this.cipher != null) { - const url = await this.changeLoginPasswordService.getChangePasswordUrl(this.cipher); + const cipher = this.cipher(); + if (cipher != null) { + const url = await this.changeLoginPasswordService.getChangePasswordUrl(cipher); if (url == null) { return; } this.platformUtilsService.launchUri(url); } }; + + /** + * Switches between two observables based on whether the user has a premium from any source. + */ + private switchPremium$( + userId: UserId, + ifPremium$: () => Observable, + ifNonPremium$: () => Observable, + ): Observable { + return this.billingAccountService + .hasPremiumFromAnySource$(userId) + .pipe(switchMap((isPremium) => (isPremium ? ifPremium$() : ifNonPremium$()))); + } + + private async checkIfPasswordIsAtRisk(cipherId: CipherId, userId: UserId): Promise { + try { + const risk = await this.cipherRiskService.computeCipherRiskForUser(cipherId, userId, true); + return isPasswordAtRisk(risk); + } catch (error: unknown) { + this.logService.error("Failed to check if password is at risk", error); + return false; + } + } } diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 02b8be552bd..be33f7a5562 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -90,13 +90,13 @@ data-testid="copy-password" (click)="logCopyEvent()" > + + + {{ "changeAtRiskPassword" | i18n }} + + + - - - {{ "changeAtRiskPassword" | i18n }} - - -
(); From 9bd7b58f6b4735829db66d7563f9f14c5482c1d3 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 4 Nov 2025 15:27:13 -0500 Subject: [PATCH 015/249] [PM-26984] Use medium instead of semibold or bold (#17188) --- .../src/auth/popup/account-switching/account.component.html | 2 +- apps/browser/src/auth/popup/components/set-pin.component.html | 2 +- apps/desktop/src/auth/components/set-pin.component.html | 2 +- .../two-factor/two-factor-setup-webauthn.component.html | 4 ++-- .../two-factor/two-factor-setup-yubikey.component.html | 2 +- .../auth/settings/two-factor/two-factor-setup.component.html | 2 +- .../webauthn-login-settings.component.html | 2 +- apps/web/src/connectors/duo-redirect.ts | 2 +- apps/web/src/connectors/webauthn-fallback.html | 2 +- apps/web/src/connectors/webauthn-mobile.html | 2 +- apps/web/src/connectors/webauthn.html | 2 +- .../device-management-item-group.component.html | 4 ++-- .../environment-selector/environment-selector.component.html | 2 +- .../src/angular/input-password/input-password.component.html | 2 +- .../login-via-auth-request.component.html | 4 ++-- .../registration-start/registration-start.component.html | 2 +- .../user-verification-form-input.component.html | 2 +- 17 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/browser/src/auth/popup/account-switching/account.component.html b/apps/browser/src/auth/popup/account-switching/account.component.html index d22ce9c9366..90770bb8d9b 100644 --- a/apps/browser/src/auth/popup/account-switching/account.component.html +++ b/apps/browser/src/auth/popup/account-switching/account.component.html @@ -25,7 +25,7 @@
( - {{ + {{ status.text }} ) diff --git a/apps/browser/src/auth/popup/components/set-pin.component.html b/apps/browser/src/auth/popup/components/set-pin.component.html index d525f9378f1..c88274b2bf4 100644 --- a/apps/browser/src/auth/popup/components/set-pin.component.html +++ b/apps/browser/src/auth/popup/components/set-pin.component.html @@ -1,6 +1,6 @@ -
+
{{ "setYourPinTitle" | i18n }}
diff --git a/apps/desktop/src/auth/components/set-pin.component.html b/apps/desktop/src/auth/components/set-pin.component.html index 6fb5829b79a..aaebf7c1cdb 100644 --- a/apps/desktop/src/auth/components/set-pin.component.html +++ b/apps/desktop/src/auth/components/set-pin.component.html @@ -1,6 +1,6 @@ -
+
{{ "unlockWithPin" | i18n }}
diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index eec9f74dd60..c272a8e5b70 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -17,10 +17,10 @@
  • - + {{ "webAuthnkeyX" | i18n: (i + 1).toString() }} - + {{ k.name }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html index dbad422a32e..172646f5d4d 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html @@ -45,7 +45,7 @@
-

{{ "nfcSupport" | i18n }}

+

{{ "nfcSupport" | i18n }}

{{ "twoFactorYubikeySupportsNfc" | i18n }} diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html index 16c3dcb3cda..ee2d4dd7b63 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html @@ -53,7 +53,7 @@

{{ p.name }} diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html index 7b1d859fb69..e022558f6b1 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html @@ -34,7 +34,7 @@ - + - - - - diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/empty-state-card.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/empty-state-card.component.html index 2ab788a0ef0..42600671e8c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/empty-state-card.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/empty-state-card.component.html @@ -2,9 +2,7 @@ class="tw-w-full tw-max-w-4xl tw-p-6 sm:tw-p-8 tw-bg-background tw-rounded-xl tw-border tw-border-solid tw-border-secondary-300 tw-flex tw-flex-col lg:tw-flex-row tw-gap-6 tw-items-center" >
-
+
{{ title() }}
@@ -22,15 +20,13 @@ class="tw-size-8 sm:tw-size-9 tw-bg-secondary-100 tw-rounded-full tw-flex tw-justify-center tw-items-center tw-flex-shrink-0" >
{{ $index + 1 }}
-
+
{{ benefit[0] }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 2476d79552f..6d030e20066 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -128,10 +128,10 @@ {{ "downloadCSV" | i18n }}
-
+
{{ "email" | i18n }}
-
+
{{ "atRiskPasswords" | i18n }}
@@ -192,10 +192,10 @@ {{ "downloadCSV" | i18n }}
-
+
{{ "application" | i18n }}
-
+
{{ "atRiskPasswords" | i18n }}
diff --git a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.html b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.html index 19a12755ca0..792606cbfe0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/organization-integrations/integration-card/integration-card.component.html @@ -18,7 +18,7 @@ @if (linkURL) { -

+

{{ name }} @if (showConnectedBadge()) { @@ -42,7 +42,7 @@ }

@if (description) { -

{{ description }}

+

{{ description }}

} @if (canSetupConnection) { + @if (variantValue === "tree") { + + + + } - + @if (variantValue !== "tree") { + + } diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 3408af3d734..5797d34da5d 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -34,7 +34,8 @@ import { SideNavService } from "./side-nav.service"; imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe], }) export class NavGroupComponent extends NavBaseComponent { - readonly nestedNavComponents = contentChildren(NavBaseComponent, { descendants: true }); + // Query direct children for hideIfEmpty functionality + readonly nestedNavComponents = contentChildren(NavBaseComponent, { descendants: false }); readonly sideNavOpen = toSignal(this.sideNavService.open$); @@ -47,6 +48,18 @@ export class NavGroupComponent extends NavBaseComponent { return this.hideActiveStyles() || this.sideNavAndGroupOpen(); }); + /** + * Determines the appropriate icon for the toggle button based on variant and open state. + * - Tree variant: Always uses 'bwi-up-solid' + * - Default variant: Uses 'bwi-angle-up' when open, 'bwi-angle-down' when closed + */ + readonly toggleButtonIcon = computed(() => { + if (this.variant() === "tree") { + return "bwi-up-solid"; + } + return this.open() ? "bwi-angle-up" : "bwi-angle-down"; + }); + /** * Allow overriding of the RouterLink['ariaCurrentWhenActive'] property. * @@ -89,14 +102,20 @@ export class NavGroupComponent extends NavBaseComponent { @Optional() @SkipSelf() private parentNavGroup: NavGroupComponent, ) { super(); + + // Set tree depth based on parent's depth + // Both NavGroups and NavItems use constructor-based depth initialization + if (this.parentNavGroup) { + this.treeDepth.set(this.parentNavGroup.treeDepth() + 1); + } } setOpen(isOpen: boolean) { this.open.set(isOpen); this.openChange.emit(this.open()); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - this.open() && this.parentNavGroup?.setOpen(this.open()); + if (this.open()) { + this.parentNavGroup?.setOpen(this.open()); + } } protected toggle(event?: MouseEvent) { diff --git a/libs/components/src/navigation/nav-group.stories.ts b/libs/components/src/navigation/nav-group.stories.ts index 9910c99fab4..fc9df19a3e2 100644 --- a/libs/components/src/navigation/nav-group.stories.ts +++ b/libs/components/src/navigation/nav-group.stories.ts @@ -132,3 +132,24 @@ export const Secondary: StoryObj = { `, }), }; + +export const Tree: StoryObj = { + render: (args) => ({ + props: args, + template: /*html*/ ` + + + + + + + + + + + + + + `, + }), +}; diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index 10f68145a4d..9f6ea3373b7 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -2,6 +2,12 @@ @let open = sideNavService.open$ | async; @if (open || icon()) {
+ @if (open) { +
+ +
+ }
- + @if (icon()) { + + } @if (open) { {{ text() }} } @@ -37,6 +56,7 @@ >; } // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -22,6 +23,18 @@ export abstract class NavGroupAbstraction { imports: [CommonModule, IconButtonModule, RouterModule], }) export class NavItemComponent extends NavBaseComponent { + /** + * Base padding for tree variant items (in rem) + * This provides the initial indentation for tree items before depth-based padding + */ + protected readonly TREE_BASE_PADDING = 1.25; + + /** + * Padding increment per tree depth level (in rem) + * Each nested level adds this amount of padding to visually indicate hierarchy + */ + protected readonly TREE_DEPTH_PADDING = 1.25; + /** Forces active styles to be shown, regardless of the `routerLinkActiveOptions` */ readonly forceActiveStyles = input(false); @@ -78,5 +91,10 @@ export class NavItemComponent extends NavBaseComponent { @Optional() private parentNavGroup: NavGroupAbstraction, ) { super(); + + // Set tree depth based on parent's depth + if (this.parentNavGroup) { + this.treeDepth.set(this.parentNavGroup.treeDepth() + 1); + } } } From c404ee210baedd82bb10b644c3271cd17e49519b Mon Sep 17 00:00:00 2001 From: Vicki League Date: Thu, 6 Nov 2025 11:27:46 -0500 Subject: [PATCH 048/249] [PM-26984] Use medium instead of semibold or bold (#17191) --- .../autofill-confirmation-dialog.component.html | 10 +++++----- .../autofill-confirmation-dialog.component.spec.ts | 2 +- .../vault-list-items-container.component.html | 2 +- .../vault/popup/settings/appearance-v2.component.html | 2 +- .../setup-extension/setup-extension.component.html | 2 +- .../components/vault-items/vault-items.component.html | 2 +- .../components/vault-filter.component.html | 2 +- .../components/spotlight/spotlight.component.html | 2 +- .../additional-options-section.component.html | 2 +- .../autofill-options/autofill-options.component.html | 2 +- .../custom-fields/custom-fields.component.html | 2 +- .../new-item-nudge/new-item-nudge.component.ts | 2 +- .../item-history/item-history-v2.component.html | 6 +++--- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html index 625c92e38c5..39ec6bc28a6 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html @@ -5,7 +5,7 @@ {{ "confirmAutofillDesc" | i18n }}

@if (savedUrls.length === 1) { -

+

{{ "savedWebsite" | i18n }}

@@ -16,14 +16,14 @@ } @if (savedUrls.length > 1) {
-

+

{{ "savedWebsites" | i18n: savedUrls.length }}

} -

+

{{ "currentWebsite" | i18n }}

@@ -61,7 +61,7 @@ bitLink linkType="secondary" (click)="close()" - class="tw-mt-2 tw-font-bold tw-text-sm tw-justify-center tw-text-center" + class="tw-mt-2 tw-font-medium tw-text-sm tw-justify-center tw-text-center" > {{ "doNotAutofill" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts index e8f00cd7b8d..52ab4adcc0c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts @@ -199,7 +199,7 @@ describe("AutofillConfirmationDialogComponent", () => { it("shows the 'view all' button when savedUrls > 1 and hides it after click", () => { const findViewAll = () => fixture.nativeElement.querySelector( - "button.tw-text-sm.tw-font-bold.tw-cursor-pointer", + "button.tw-text-sm.tw-font-medium.tw-cursor-pointer", ) as HTMLButtonElement | null; let btn = findViewAll(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index fad5615764c..3dac158b8e1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -84,7 +84,7 @@ -

+

{{ group.subHeaderKey | i18n }}

diff --git a/apps/browser/src/vault/popup/settings/appearance-v2.component.html b/apps/browser/src/vault/popup/settings/appearance-v2.component.html index c9598c76db0..b58316a8d64 100644 --- a/apps/browser/src/vault/popup/settings/appearance-v2.component.html +++ b/apps/browser/src/vault/popup/settings/appearance-v2.component.html @@ -41,7 +41,7 @@ {{ "showAnimations" | i18n }} -

{{ "vaultCustomization" | i18n }}

+

{{ "vaultCustomization" | i18n }}

diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html index 09bd38c8517..038c258d4b6 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html @@ -6,7 +6,7 @@ >
-

+

{{ "setupExtensionPageTitle" | i18n }}

diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index d6b5fafe6ec..cb2af9a64e5 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -12,7 +12,7 @@ (change)="$event ? toggleAll() : null" [checked]="selection.hasValue() && isAllSelected" /> -

{{ "filters" | i18n }} diff --git a/libs/angular/src/vault/components/spotlight/spotlight.component.html b/libs/angular/src/vault/components/spotlight/spotlight.component.html index 0d0e95e191b..720bf5c1908 100644 --- a/libs/angular/src/vault/components/spotlight/spotlight.component.html +++ b/libs/angular/src/vault/components/spotlight/spotlight.component.html @@ -3,7 +3,7 @@ >
-

{{ title }}

+

{{ title }}

- + {{ "addField" | i18n }} diff --git a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html index b83685aac94..f71ed1c53d3 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html +++ b/libs/vault/src/cipher-form/components/autofill-options/autofill-options.component.html @@ -29,7 +29,7 @@ (click)="addUri({ uri: null, matchDetection: null }, true)" *ngIf="autofillOptionsForm.enabled" > - + {{ "addWebsite" | i18n }} diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html index c5c1b752aef..2ba8b0c76d3 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html @@ -119,7 +119,7 @@ data-testid="add-field-button" *ngIf="!isPartialEdit && !parentFormDisabled" > - + {{ "addField" | i18n }} diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 5f4a44e5ef5..0c85cad5cfb 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -72,7 +72,7 @@ export class NewItemNudgeComponent { this.dismissalNudgeType = NudgeType.NewSshItemStatus; this.nudgeTitle = this.i18nService.t("newSshNudgeTitle"); - this.nudgeBody = `${sshPartOne} ${sshPartTwo}`; + this.nudgeBody = `${sshPartOne} ${sshPartTwo}`; return NudgeType.NewSshItemStatus; } default: diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index 9395fb34fc7..c5963638b80 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -4,7 +4,7 @@

- {{ "lastEdited" | i18n }}: + {{ "lastEdited" | i18n }}: {{ cipher.revisionDate | date: "medium" }}

- {{ "dateCreated" | i18n }}: + {{ "dateCreated" | i18n }}: {{ cipher.creationDate | date: "medium" }}

- {{ "datePasswordUpdated" | i18n }}: + {{ "datePasswordUpdated" | i18n }}: {{ cipher.passwordRevisionDisplayDate | date: "medium" }}

-
- } - - @if (renderMode === renderModes.criticalAppsWithAtRiskAppsAndTasks) { -
- {{ "percentageCompleted" | i18n: completedPercent }} -
- -
- {{ - "securityTasksCompleted" | i18n: completedTasksCount : totalTasksCount - }} -
- -
-
-
{{ completedTasksCount }}
-
{{ totalTasksCount }}
+ @switch (currentView()) { + @case (PasswordChangeViewEnum.EMPTY) { +
+ {{ "assignMembersTasksToMonitorProgress" | i18n }}
-
- - - - +
+ {{ "onceYouReviewApps" | i18n }} +
+ } + + @case (PasswordChangeViewEnum.NO_TASKS_ASSIGNED) { +
+ {{ "assignMembersTasksToMonitorProgress" | i18n }} +
+ +
+ {{ + "countOfAtRiskPasswords" | i18n: atRiskPasswordCount() + }} +
+ + @if (atRiskPasswordCount() > 0) { +
+ +
+ } + } + + @case (PasswordChangeViewEnum.NEW_TASKS_AVAILABLE) { +
+ {{ "assignMembersTasksToMonitorProgress" | i18n }} +
+ +
+ {{ "newPasswordsAtRisk" | i18n: atRiskPasswordCount() }} +
+ +
+ +
+ } + + @case (PasswordChangeViewEnum.PROGRESS) { +
+ {{ "percentageCompleted" | i18n: completedTasksPercent() }} +
+ +
+ {{ + "securityTasksCompleted" | i18n: completedTasksCount() : tasksCount() + }} +
+ +
+
+
{{ completedTasksCount() }}
+
{{ tasksCount() }}
+
+
+ + + } }
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts index 5c03534720e..509b3e1314a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts @@ -1,197 +1,169 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, - ChangeDetectorRef, Component, DestroyRef, + Injector, OnInit, + Signal, + computed, + effect, inject, + input, + signal, } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute } from "@angular/router"; -import { switchMap, of, BehaviorSubject, combineLatest } from "rxjs"; +import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; +import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AllActivitiesService, - ApplicationHealthReportDetailEnriched, - SecurityTasksApiService, - TaskMetrics, - OrganizationReportSummary, + RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { OrganizationId } from "@bitwarden/common/types/guid"; -import { ButtonModule, ProgressModule, TypographyModule } from "@bitwarden/components"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTask, SecurityTaskStatus } from "@bitwarden/common/vault/tasks"; +import { + ButtonModule, + ProgressModule, + ToastService, + TypographyModule, +} from "@bitwarden/components"; -import { DefaultAdminTaskService } from "../../../../vault/services/default-admin-task.service"; -import { RenderMode } from "../../models/activity.models"; import { AccessIntelligenceSecurityTasksService } from "../../shared/security-tasks.service"; +export const PasswordChangeView = { + EMPTY: "empty", + NO_TASKS_ASSIGNED: "noTasksAssigned", + NEW_TASKS_AVAILABLE: "newTasks", + PROGRESS: "progress", +} as const; + +export type PasswordChangeView = (typeof PasswordChangeView)[keyof typeof PasswordChangeView]; + @Component({ changeDetection: ChangeDetectionStrategy.OnPush, selector: "dirt-password-change-metric", imports: [CommonModule, TypographyModule, JslibModule, ProgressModule, ButtonModule], templateUrl: "./password-change-metric.component.html", - providers: [AccessIntelligenceSecurityTasksService, DefaultAdminTaskService], }) export class PasswordChangeMetricComponent implements OnInit { + PasswordChangeViewEnum = PasswordChangeView; + private destroyRef = inject(DestroyRef); - protected taskMetrics$ = new BehaviorSubject({ totalTasks: 0, completedTasks: 0 }); - private completedTasks: number = 0; - private totalTasks: number = 0; - private allApplicationsDetails: ApplicationHealthReportDetailEnriched[] = []; + // Inputs + // Prefer component input since route param controls UI state + readonly organizationId = input.required(); - atRiskAppsCount: number = 0; - atRiskPasswordsCount: number = 0; - private organizationId!: OrganizationId; - renderMode: RenderMode = "noCriticalApps"; + // Signal states + private readonly _tasks: Signal = signal([]); + private readonly _atRiskCipherIds: Signal = signal([]); + private readonly _hasCriticalApplications: Signal = signal(false); - // Computed properties (formerly getters) - updated when data changes - protected completedPercent = 0; - protected completedTasksCount = 0; - protected totalTasksCount = 0; - protected canAssignTasks = false; - protected hasExistingTasks = false; - protected newAtRiskPasswordsCount = 0; + // Computed properties + readonly tasksCount = computed(() => this._tasks().length); + readonly completedTasksCount = computed( + () => this._tasks().filter((task) => task.status === SecurityTaskStatus.Completed).length, + ); + readonly uncompletedTasksCount = computed( + () => this._tasks().filter((task) => task.status == SecurityTaskStatus.Pending).length, + ); + readonly completedTasksPercent = computed(() => { + const total = this.tasksCount(); + // Account for case where there are no tasks to avoid NaN + return total > 0 ? Math.round((this.completedTasksCount() / total) * 100) : 0; + }); + + readonly atRiskPasswordCount = computed(() => { + const atRiskIds = this._atRiskCipherIds(); + const tasks = this._tasks(); + + if (tasks.length === 0) { + return atRiskIds.length; + } + + const assignedIdSet = new Set(tasks.map((task) => task.cipherId)); + const unassignedIds = atRiskIds.filter((id) => !assignedIdSet.has(id)); + + return unassignedIds.length; + }); + + readonly currentView = computed(() => { + if (!this._hasCriticalApplications()) { + return PasswordChangeView.EMPTY; + } + if (this.tasksCount() === 0) { + return PasswordChangeView.NO_TASKS_ASSIGNED; + } + if (this.atRiskPasswordCount() > 0) { + return PasswordChangeView.NEW_TASKS_AVAILABLE; + } + return PasswordChangeView.PROGRESS; + }); constructor( - private activatedRoute: ActivatedRoute, - private securityTasksApiService: SecurityTasksApiService, private allActivitiesService: AllActivitiesService, - protected accessIntelligenceSecurityTasksService: AccessIntelligenceSecurityTasksService, - private cdr: ChangeDetectorRef, - ) {} + private i18nService: I18nService, + private injector: Injector, + private riskInsightsDataService: RiskInsightsDataService, + protected securityTasksService: AccessIntelligenceSecurityTasksService, + private toastService: ToastService, + ) { + // Setup the _tasks signal by manually passing in the injector + this._tasks = toSignal(this.securityTasksService.tasks$, { + initialValue: [], + injector: this.injector, + }); + // Setup the _atRiskCipherIds signal by manually passing in the injector + this._atRiskCipherIds = toSignal( + this.riskInsightsDataService.criticalApplicationAtRiskCipherIds$, + { + initialValue: [], + injector: this.injector, + }, + ); + + this._hasCriticalApplications = toSignal( + this.riskInsightsDataService.criticalReportResults$.pipe( + takeUntilDestroyed(this.destroyRef), + map((report) => { + return report != null && (report.reportData?.length ?? 0) > 0; + }), + ), + { + initialValue: false, + injector: this.injector, + }, + ); + + effect(() => { + const isShowingProgress = this.currentView() === PasswordChangeView.PROGRESS; + this.allActivitiesService.setExtendPasswordWidget(isShowingProgress); + }); + } async ngOnInit(): Promise { - combineLatest([this.activatedRoute.paramMap, this.allActivitiesService.taskCreatedCount$]) - .pipe( - switchMap(([params, _]) => { - const orgId = params.get("organizationId"); - if (orgId) { - this.organizationId = orgId as OrganizationId; - return this.securityTasksApiService.getTaskMetrics(this.organizationId); - } - return of({ totalTasks: 0, completedTasks: 0 }); - }), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe((metrics) => { - this.taskMetrics$.next(metrics); - this.cdr.markForCheck(); - }); - - combineLatest([ - this.taskMetrics$, - this.allActivitiesService.reportSummary$, - this.allActivitiesService.atRiskPasswordsCount$, - this.allActivitiesService.allApplicationsDetails$, - ]) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(([taskMetrics, summary, atRiskPasswordsCount, allApplicationsDetails]) => { - this.atRiskAppsCount = summary.totalCriticalAtRiskApplicationCount; - this.atRiskPasswordsCount = atRiskPasswordsCount; - this.completedTasks = taskMetrics.completedTasks; - this.totalTasks = taskMetrics.totalTasks; - this.allApplicationsDetails = allApplicationsDetails; - - // Determine render mode based on state - this.renderMode = this.determineRenderMode(summary, taskMetrics, atRiskPasswordsCount); - - this.allActivitiesService.setPasswordChangeProgressMetricHasProgressBar( - this.renderMode === RenderMode.criticalAppsWithAtRiskAppsAndTasks, - ); - - // Update all computed properties when data changes - this.updateComputedProperties(); - - this.cdr.markForCheck(); - }); - } - - private determineRenderMode( - summary: OrganizationReportSummary, - taskMetrics: TaskMetrics, - atRiskPasswordsCount: number, - ): RenderMode { - // State 1: No critical apps setup - if (summary.totalCriticalApplicationCount === 0) { - return RenderMode.noCriticalApps; - } - - // State 2: Critical apps with at-risk passwords but no tasks assigned yet - // OR tasks exist but NEW at-risk passwords detected (more at-risk passwords than tasks) - if ( - summary.totalCriticalApplicationCount > 0 && - (taskMetrics.totalTasks === 0 || atRiskPasswordsCount > taskMetrics.totalTasks) - ) { - return RenderMode.criticalAppsWithAtRiskAppsAndNoTasks; - } - - // State 3: Critical apps with at-risk apps and tasks (progress tracking) - if ( - summary.totalCriticalApplicationCount > 0 && - taskMetrics.totalTasks > 0 && - atRiskPasswordsCount <= taskMetrics.totalTasks - ) { - return RenderMode.criticalAppsWithAtRiskAppsAndTasks; - } - - // Default to no critical apps - return RenderMode.noCriticalApps; - } - - /** - * Updates all computed properties based on current state. - * Called whenever data changes to avoid recalculation on every change detection cycle. - */ - private updateComputedProperties(): void { - // Calculate completion percentage - this.completedPercent = - this.totalTasks === 0 ? 0 : Math.round((this.completedTasks / this.totalTasks) * 100); - - // Calculate completed tasks count based on render mode - switch (this.renderMode) { - case RenderMode.noCriticalApps: - case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: - this.completedTasksCount = 0; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndTasks: - this.completedTasksCount = this.completedTasks; - break; - default: - this.completedTasksCount = 0; - } - - // Calculate total tasks count based on render mode - switch (this.renderMode) { - case RenderMode.noCriticalApps: - this.totalTasksCount = 0; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndNoTasks: - this.totalTasksCount = this.atRiskAppsCount; - break; - case RenderMode.criticalAppsWithAtRiskAppsAndTasks: - this.totalTasksCount = this.totalTasks; - break; - default: - this.totalTasksCount = 0; - } - - // Calculate flags and counts - this.canAssignTasks = this.atRiskPasswordsCount > this.totalTasks; - this.hasExistingTasks = this.totalTasks > 0; - this.newAtRiskPasswordsCount = - this.atRiskPasswordsCount > this.totalTasks ? this.atRiskPasswordsCount - this.totalTasks : 0; - } - - get renderModes() { - return RenderMode; + await this.securityTasksService.loadTasks(this.organizationId()); } async assignTasks() { - await this.accessIntelligenceSecurityTasksService.assignTasks( - this.organizationId, - this.allApplicationsDetails.filter((app) => app.isMarkedAsCritical), - ); + try { + await this.securityTasksService.requestPasswordChangeForCriticalApplications( + this.organizationId(), + this._atRiskCipherIds(), + ); + this.toastService.showToast({ + message: this.i18nService.t("notifiedMembers"), + variant: "success", + title: this.i18nService.t("success"), + }); + } catch { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + } } } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index 8cdb927ab65..d0751556517 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -4,8 +4,10 @@
    -
  • - +
  • +
  • diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts index 7e737be0bdc..06073d93c85 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.ts @@ -1,7 +1,7 @@ -import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { Component, DestroyRef, inject, input, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, lastValueFrom } from "rxjs"; +import { lastValueFrom } from "rxjs"; import { AllActivitiesService, @@ -10,10 +10,6 @@ import { RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { getById } from "@bitwarden/common/platform/misc"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; @@ -37,13 +33,15 @@ import { NewApplicationsDialogComponent } from "./application-review-dialog/new- templateUrl: "./all-activity.component.html", }) export class AllActivityComponent implements OnInit { - organization: Organization | null = null; + // Prefer component input since route param controls UI state + readonly organizationId = input.required(); + totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; totalCriticalAppsAtRiskCount = 0; newApplicationsCount = 0; newApplications: ApplicationHealthReportDetail[] = []; - passwordChangeMetricHasProgressBar = false; + extendPasswordChangeWidget = false; allAppsHaveReviewDate = false; isAllCaughtUp = false; hasLoadedApplicationData = false; @@ -53,7 +51,6 @@ export class AllActivityComponent implements OnInit { protected ReportStatusEnum = ReportStatus; constructor( - private accountService: AccountService, protected activatedRoute: ActivatedRoute, protected allActivitiesService: AllActivitiesService, protected dataService: RiskInsightsDataService, @@ -62,53 +59,43 @@ export class AllActivityComponent implements OnInit { ) {} async ngOnInit(): Promise { - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); - const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.allActivitiesService.reportSummary$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((summary) => { + this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; + this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; + this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; + }); - if (organizationId) { - this.organization = - (await firstValueFrom( - this.organizationService.organizations$(userId).pipe(getById(organizationId)), - )) ?? null; + this.dataService.newApplications$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((newApps) => { + this.newApplications = newApps; + this.newApplicationsCount = newApps.length; + this.updateIsAllCaughtUp(); + }); - this.allActivitiesService.reportSummary$ - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((summary) => { - this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; - this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; - this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; - }); + this.allActivitiesService.extendPasswordChangeWidget$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((hasProgressBar) => { + this.extendPasswordChangeWidget = hasProgressBar; + }); - this.dataService.newApplications$ - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((newApps) => { - this.newApplications = newApps; - this.newApplicationsCount = newApps.length; - this.updateIsAllCaughtUp(); - }); - - this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$ - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((hasProgressBar) => { - this.passwordChangeMetricHasProgressBar = hasProgressBar; - }); - - this.dataService.enrichedReportData$ - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe((enrichedData) => { - if (enrichedData?.applicationData && enrichedData.applicationData.length > 0) { - this.hasLoadedApplicationData = true; - // Check if all apps have a review date (not null and not undefined) - this.allAppsHaveReviewDate = enrichedData.applicationData.every( - (app) => app.reviewedDate !== null && app.reviewedDate !== undefined, - ); - } else { - this.hasLoadedApplicationData = enrichedData !== null; - this.allAppsHaveReviewDate = false; - } - this.updateIsAllCaughtUp(); - }); - } + this.dataService.enrichedReportData$ + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((enrichedData) => { + if (enrichedData?.applicationData && enrichedData.applicationData.length > 0) { + this.hasLoadedApplicationData = true; + // Check if all apps have a review date (not null and not undefined) + this.allAppsHaveReviewDate = enrichedData.applicationData.every( + (app) => app.reviewedDate !== null && app.reviewedDate !== undefined, + ); + } else { + this.hasLoadedApplicationData = enrichedData !== null; + this.allAppsHaveReviewDate = false; + } + this.updateIsAllCaughtUp(); + }); } /** diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.ts index ac1b241a54b..15d927a7714 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.ts @@ -10,9 +10,6 @@ import { import { I18nPipe } from "@bitwarden/ui-common"; import { DarkImageSourceDirective } from "@bitwarden/vault"; -import { DefaultAdminTaskService } from "../../../../vault/services/default-admin-task.service"; -import { AccessIntelligenceSecurityTasksService } from "../../shared/security-tasks.service"; - /** * Embedded component for displaying task assignment UI. * Not a dialog - intended to be embedded within a parent dialog. @@ -36,7 +33,6 @@ import { AccessIntelligenceSecurityTasksService } from "../../shared/security-ta DarkImageSourceDirective, CalloutComponent, ], - providers: [AccessIntelligenceSecurityTasksService, DefaultAdminTaskService], }) export class AssignTasksViewComponent { readonly criticalApplicationsCount = input.required(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts index 8a1a90245b3..e415fbf9ad0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts @@ -8,12 +8,10 @@ import { signal, } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { from, switchMap } from "rxjs"; +import { from, switchMap, take } from "rxjs"; import { ApplicationHealthReportDetail, - ApplicationHealthReportDetailEnriched, - OrganizationReportApplication, RiskInsightsDataService, } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { getUniqueMembers } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers"; @@ -209,40 +207,16 @@ export class NewApplicationsDialogComponent { } this.saving.set(true); - // Create updated organization report application types with new review date - // and critical marking based on selected applications - const newReviewDate = new Date(); - const updatedApplications: OrganizationReportApplication[] = - this.dialogParams.newApplications.map((app) => ({ - applicationName: app.applicationName, - isCritical: this.selectedApplications().has(app.applicationName), - reviewedDate: newReviewDate, - })); - // Save the application review dates and critical markings - this.dataService - .saveApplicationReviewStatus(updatedApplications) + this.dataService.criticalApplicationAtRiskCipherIds$ .pipe( - takeUntilDestroyed(this.destroyRef), - switchMap((updatedState) => { - // After initial save is complete, created the assigned tasks - // for at risk passwords - const updatedStateApplicationData = updatedState?.data?.applicationData || []; - // Manual enrich for type matching - // TODO Consolidate in model updates - const manualEnrichedApplications = - updatedState?.data?.reportData.map( - (application): ApplicationHealthReportDetailEnriched => ({ - ...application, - isMarkedAsCritical: updatedStateApplicationData.some( - (a) => a.applicationName == application.applicationName && a.isCritical, - ), - }), - ) || []; + takeUntilDestroyed(this.destroyRef), // Satisfy eslint rule + take(1), // Handle unsubscribe for one off operation + switchMap((criticalApplicationAtRiskCipherIds) => { return from( - this.accessIntelligenceSecurityTasksService.assignTasks( + this.accessIntelligenceSecurityTasksService.requestPasswordChangeForCriticalApplications( this.dialogParams.organizationId, - manualEnrichedApplications, + criticalApplicationAtRiskCipherIds, ), ); }), diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts index 794df90da53..7b7ca8c42da 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts @@ -4,7 +4,7 @@ import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { debounceTime, EMPTY, map, switchMap } from "rxjs"; +import { debounceTime, EMPTY, from, map, switchMap, take } from "rxjs"; import { Security } from "@bitwarden/assets/svg"; import { @@ -23,7 +23,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; -import { DefaultAdminTaskService } from "../../../vault/services/default-admin-task.service"; import { RiskInsightsTabType } from "../models/risk-insights.models"; import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component"; import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks.service"; @@ -42,7 +41,6 @@ import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks SharedModule, AppTableRowScrollableComponent, ], - providers: [AccessIntelligenceSecurityTasksService, DefaultAdminTaskService], }) export class CriticalApplicationsComponent implements OnInit { private destroyRef = inject(DestroyRef); @@ -58,13 +56,13 @@ export class CriticalApplicationsComponent implements OnInit { constructor( protected activatedRoute: ActivatedRoute, - protected router: Router, - protected toastService: ToastService, protected dataService: RiskInsightsDataService, protected criticalAppsService: CriticalAppsService, - protected reportService: RiskInsightsReportService, protected i18nService: I18nService, - private accessIntelligenceSecurityTasksService: AccessIntelligenceSecurityTasksService, + protected reportService: RiskInsightsReportService, + protected router: Router, + private securityTasksService: AccessIntelligenceSecurityTasksService, + protected toastService: ToastService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -131,10 +129,35 @@ export class CriticalApplicationsComponent implements OnInit { }; async requestPasswordChange() { - await this.accessIntelligenceSecurityTasksService.assignTasks( - this.organizationId, - this.dataSource.data, - ); + this.dataService.criticalApplicationAtRiskCipherIds$ + .pipe( + takeUntilDestroyed(this.destroyRef), // Satisfy eslint rule + take(1), // Handle unsubscribe for one off operation + switchMap((cipherIds) => { + return from( + this.securityTasksService.requestPasswordChangeForCriticalApplications( + this.organizationId, + cipherIds, + ), + ); + }), + ) + .subscribe({ + next: () => { + this.toastService.showToast({ + message: this.i18nService.t("notifiedMembers"), + variant: "success", + title: this.i18nService.t("success"), + }); + }, + error: () => { + this.toastService.showToast({ + message: this.i18nService.t("unexpectedError"), + variant: "error", + title: this.i18nService.t("error"), + }); + }, + }); } showAppAtRiskMembers = async (applicationName: string) => { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/models/activity.models.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/models/activity.models.ts deleted file mode 100644 index 6f108a46029..00000000000 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/models/activity.models.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const RenderMode = { - noCriticalApps: "noCriticalApps", - criticalAppsWithAtRiskAppsAndNoTasks: "criticalAppsWithAtRiskAppsAndNoTasks", - criticalAppsWithAtRiskAppsAndTasks: "criticalAppsWithAtRiskAppsAndTasks", -} as const; - -export type RenderMode = (typeof RenderMode)[keyof typeof RenderMode]; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index fad2afb6e38..4b7d51af174 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -87,7 +87,7 @@ @if (isRiskInsightsActivityTabFeatureEnabled) { - + } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index 3bc968dabc1..5a5efa8225d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -78,7 +78,7 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { appsCount: number = 0; - private organizationId: OrganizationId = "" as OrganizationId; + protected organizationId: OrganizationId = "" as OrganizationId; dataLastUpdated: Date | null = null; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts index 22f8ea55f51..f6fb41cdbb0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.spec.ts @@ -1,14 +1,9 @@ import { TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; -import { - AllActivitiesService, - ApplicationHealthReportDetailEnriched, -} from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { OrganizationId } from "@bitwarden/common/types/guid"; +import { SecurityTasksApiService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; import { SecurityTaskType } from "@bitwarden/common/vault/tasks"; -import { ToastService } from "@bitwarden/components"; import { DefaultAdminTaskService } from "../../../vault/services/default-admin-task.service"; @@ -16,18 +11,14 @@ import { AccessIntelligenceSecurityTasksService } from "./security-tasks.service describe("AccessIntelligenceSecurityTasksService", () => { let service: AccessIntelligenceSecurityTasksService; - const defaultAdminTaskServiceSpy = mock(); - const allActivitiesServiceSpy = mock(); - const toastServiceSpy = mock(); - const i18nServiceSpy = mock(); + const defaultAdminTaskServiceMock = mock(); + const securityTasksApiServiceMock = mock(); beforeEach(() => { TestBed.configureTestingModule({}); service = new AccessIntelligenceSecurityTasksService( - allActivitiesServiceSpy, - defaultAdminTaskServiceSpy, - toastServiceSpy, - i18nServiceSpy, + defaultAdminTaskServiceMock, + securityTasksApiServiceMock, ); }); @@ -36,104 +27,48 @@ describe("AccessIntelligenceSecurityTasksService", () => { }); describe("assignTasks", () => { - it("should call requestPasswordChange and setTaskCreatedCount", async () => { + it("should call requestPasswordChangeForCriticalApplications and setTaskCreatedCount", async () => { + // Set up test data const organizationId = "org-1" as OrganizationId; - const apps = [ - { - isMarkedAsCritical: true, - atRiskPasswordCount: 1, - atRiskCipherIds: ["cid1"], - } as ApplicationHealthReportDetailEnriched, - ]; - const spy = jest.spyOn(service, "requestPasswordChange").mockResolvedValue(2); - await service.assignTasks(organizationId, apps); - expect(spy).toHaveBeenCalledWith(organizationId, apps); - expect(allActivitiesServiceSpy.setTaskCreatedCount).toHaveBeenCalledWith(2); + const mockCipherIds = ["cid1" as CipherId, "cid2" as CipherId]; + const spy = jest.spyOn(service, "requestPasswordChangeForCriticalApplications"); + + // Call the method + await service.requestPasswordChangeForCriticalApplications(organizationId, mockCipherIds); + + // Verify that the method was called with correct parameters + expect(spy).toHaveBeenCalledWith(organizationId, mockCipherIds); }); }); - describe("requestPasswordChange", () => { + describe("requestPasswordChangeForCriticalApplications", () => { it("should create tasks for distinct cipher ids and show success toast", async () => { + // Set up test data const organizationId = "org-2" as OrganizationId; - const apps = [ - { - isMarkedAsCritical: true, - atRiskPasswordCount: 2, - atRiskCipherIds: ["cid1", "cid2"], - } as ApplicationHealthReportDetailEnriched, - { - isMarkedAsCritical: true, - atRiskPasswordCount: 1, - atRiskCipherIds: ["cid2"], - } as ApplicationHealthReportDetailEnriched, - ]; - defaultAdminTaskServiceSpy.bulkCreateTasks.mockResolvedValue(undefined); - i18nServiceSpy.t.mockImplementation((key) => key); + const mockCipherIds = ["cid1" as CipherId, "cid2" as CipherId]; + defaultAdminTaskServiceMock.bulkCreateTasks.mockResolvedValue(undefined); + const spy = jest.spyOn(service, "requestPasswordChangeForCriticalApplications"); - const result = await service.requestPasswordChange(organizationId, apps); + // Call the method + await service.requestPasswordChangeForCriticalApplications(organizationId, mockCipherIds); - expect(defaultAdminTaskServiceSpy.bulkCreateTasks).toHaveBeenCalledWith(organizationId, [ + // Verify that bulkCreateTasks was called with distinct cipher ids + expect(defaultAdminTaskServiceMock.bulkCreateTasks).toHaveBeenCalledWith(organizationId, [ { cipherId: "cid1", type: SecurityTaskType.UpdateAtRiskCredential }, { cipherId: "cid2", type: SecurityTaskType.UpdateAtRiskCredential }, ]); - expect(toastServiceSpy.showToast).toHaveBeenCalledWith({ - message: "notifiedMembers", - variant: "success", - title: "success", - }); - expect(result).toBe(2); + // Verify that the method was called with correct parameters + expect(spy).toHaveBeenCalledWith(organizationId, mockCipherIds); }); - it("should show error toast and return 0 if bulkCreateTasks throws", async () => { + it("should handle error if defaultAdminTaskService errors", async () => { const organizationId = "org-3" as OrganizationId; - const apps = [ - { - isMarkedAsCritical: true, - atRiskPasswordCount: 1, - atRiskCipherIds: ["cid3"], - } as ApplicationHealthReportDetailEnriched, - ]; - defaultAdminTaskServiceSpy.bulkCreateTasks.mockRejectedValue(new Error("fail")); - i18nServiceSpy.t.mockImplementation((key) => key); + const mockCipherIds = ["cid3" as CipherId]; + defaultAdminTaskServiceMock.bulkCreateTasks.mockRejectedValue(new Error("API fail error")); - const result = await service.requestPasswordChange(organizationId, apps); - - expect(toastServiceSpy.showToast).toHaveBeenCalledWith({ - message: "unexpectedError", - variant: "error", - title: "error", - }); - expect(result).toBe(0); - }); - - it("should not create any tasks if no apps have atRiskPasswordCount > 0", async () => { - const organizationId = "org-4" as OrganizationId; - const apps = [ - { - isMarkedAsCritical: true, - atRiskPasswordCount: 0, - atRiskCipherIds: ["cid4"], - } as ApplicationHealthReportDetailEnriched, - ]; - const result = await service.requestPasswordChange(organizationId, apps); - - expect(defaultAdminTaskServiceSpy.bulkCreateTasks).toHaveBeenCalledWith(organizationId, []); - expect(result).toBe(0); - }); - - it("should not create any tasks for non-critical apps", async () => { - const organizationId = "org-5" as OrganizationId; - const apps = [ - { - isMarkedAsCritical: false, - atRiskPasswordCount: 2, - atRiskCipherIds: ["cid5", "cid6"], - } as ApplicationHealthReportDetailEnriched, - ]; - const result = await service.requestPasswordChange(organizationId, apps); - - expect(defaultAdminTaskServiceSpy.bulkCreateTasks).toHaveBeenCalledWith(organizationId, []); - expect(result).toBe(0); + await expect( + service.requestPasswordChangeForCriticalApplications(organizationId, mockCipherIds), + ).rejects.toThrow("API fail error"); }); }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts index 4d7a41007eb..688ab039ca9 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/security-tasks.service.ts @@ -1,64 +1,63 @@ -import { Injectable } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; -import { - AllActivitiesService, - ApplicationHealthReportDetailEnriched, -} from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { SecurityTasksApiService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { CipherId, OrganizationId } from "@bitwarden/common/types/guid"; -import { SecurityTaskType } from "@bitwarden/common/vault/tasks"; -import { ToastService } from "@bitwarden/components"; +import { SecurityTask, SecurityTaskType } from "@bitwarden/common/vault/tasks"; import { CreateTasksRequest } from "../../../vault/services/abstractions/admin-task.abstraction"; import { DefaultAdminTaskService } from "../../../vault/services/default-admin-task.service"; -@Injectable() +/** + * Service for managing security tasks related to Access Intelligence features + */ export class AccessIntelligenceSecurityTasksService { + private _tasksSubject$ = new BehaviorSubject([]); + tasks$ = this._tasksSubject$.asObservable(); + constructor( - private allActivitiesService: AllActivitiesService, private adminTaskService: DefaultAdminTaskService, - private toastService: ToastService, - private i18nService: I18nService, + private securityTasksApiService: SecurityTasksApiService, ) {} - async assignTasks(organizationId: OrganizationId, apps: ApplicationHealthReportDetailEnriched[]) { - const taskCount = await this.requestPasswordChange(organizationId, apps); - this.allActivitiesService.setTaskCreatedCount(taskCount); + + /** + * Gets security task metrics for the given organization + * + * @param organizationId The organization ID + * @returns Metrics about security tasks such as a count of completed and total tasks + */ + getTaskMetrics(organizationId: OrganizationId) { + return this.securityTasksApiService.getTaskMetrics(organizationId); } - // TODO: this method is shared between here and critical-applications.component.ts - async requestPasswordChange( + /** + * Loads security tasks for the given organization and updates the internal tasks subject + * + * @param organizationId The organization ID + */ + async loadTasks(organizationId: OrganizationId): Promise { + // Loads the tasks to update the service + const tasks = await this.securityTasksApiService.getAllTasks(organizationId); + this._tasksSubject$.next(tasks); + } + + /** + * Bulk assigns password change tasks for critical applications with at-risk passwords + * + * @param organizationId The organization ID + * @param criticalApplicationIds IDs of critical applications with at-risk passwords + */ + async requestPasswordChangeForCriticalApplications( organizationId: OrganizationId, - apps: ApplicationHealthReportDetailEnriched[], - ): Promise { - // Only create tasks for CRITICAL applications with at-risk passwords - const cipherIds = apps - .filter((_) => _.isMarkedAsCritical && _.atRiskPasswordCount > 0) - .flatMap((app) => app.atRiskCipherIds); - - const distinctCipherIds = Array.from(new Set(cipherIds)); - + criticalApplicationIds: CipherId[], + ) { + const distinctCipherIds = Array.from(new Set(criticalApplicationIds)); const tasks: CreateTasksRequest[] = distinctCipherIds.map((cipherId) => ({ - cipherId: cipherId as CipherId, + cipherId, type: SecurityTaskType.UpdateAtRiskCredential, })); - try { - await this.adminTaskService.bulkCreateTasks(organizationId, tasks); - this.toastService.showToast({ - message: this.i18nService.t("notifiedMembers"), - variant: "success", - title: this.i18nService.t("success"), - }); - - return tasks.length; - } catch { - this.toastService.showToast({ - message: this.i18nService.t("unexpectedError"), - variant: "error", - title: this.i18nService.t("error"), - }); - } - - return 0; + await this.adminTaskService.bulkCreateTasks(organizationId, tasks); + // Reload tasks after creation + await this.loadTasks(organizationId); } } From 19626d1b3eece71ed6fa59c46484780ee2bd81d4 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Fri, 7 Nov 2025 15:58:39 -0500 Subject: [PATCH 065/249] [PM-26363] Add one time setup dialog for auto confirm (#17104) * add one time setup dialog for auto confirm * add one time setup dialog for auto confirm * fix copy, padding, cleanup observable logic * cleanup * cleanup * refactor * clean up * more cleanup * Fix deleted files This reverts commit 7c18a5e5123f90e74fad3f766a74b9d13bec234b. --- ...-confirm-edit-policy-dialog.component.html | 8 +- ...to-confirm-edit-policy-dialog.component.ts | 20 ++++- .../organizations/policies/index.ts | 3 + .../auto-confirm-policy.component.html | 6 +- apps/web/src/app/core/core.module.ts | 32 +++++++- .../vault/individual-vault/vault.component.ts | 81 +++++++++++++++++++ apps/web/src/locales/en/messages.json | 14 ++-- .../models/auto-confirm-state.model.ts | 2 +- .../services/policy/default-policy.service.ts | 2 + libs/state/src/core/state-definitions.ts | 2 +- 10 files changed, 151 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.html b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.html index b85f79f6038..4d1db65034d 100644 --- a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.html @@ -38,11 +38,11 @@
    @let showBadge = firstTimeDialog(); @if (showBadge) { - {{ "availableNow" | i18n }} + {{ "availableNow" | i18n }} } - {{ (firstTimeDialog ? "autoConfirm" : "editPolicy") | i18n }} - @if (!firstTimeDialog) { + {{ (showBadge ? "autoConfirm" : "editPolicy") | i18n }} + @if (!showBadge) { {{ policy.name | i18n }} @@ -64,7 +64,7 @@ type="submit" > @let autoConfirmEnabled = autoConfirmEnabled$ | async; - @let managePoliciesOnly = managePolicies$ | async; + @let managePoliciesOnly = managePoliciesOnly$ | async; @if (autoConfirmEnabled || managePoliciesOnly) { {{ "save" | i18n }} } @else { diff --git a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts index 179dda5a5f4..bdc664e208e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts @@ -22,6 +22,7 @@ import { tap, } from "rxjs"; +import { AutomaticUserConfirmationService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -85,7 +86,10 @@ export class AutoConfirmPolicyDialogComponent switchMap((userId) => this.policyService.policies$(userId)), map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm)?.enabled ?? false), ); - protected managePolicies$: Observable = this.accountService.activeAccount$.pipe( + // Users with manage policies custom permission should not see the dialog's second step since + // they do not have permission to configure the setting. This will only allow them to configure + // the policy. + protected managePoliciesOnly$: Observable = this.accountService.activeAccount$.pipe( getUserId, switchMap((userId) => this.organizationService.organizations$(userId)), getById(this.data.organizationId), @@ -116,6 +120,7 @@ export class AutoConfirmPolicyDialogComponent private organizationService: OrganizationService, private policyService: PolicyService, private router: Router, + private autoConfirmService: AutomaticUserConfirmationService, ) { super( data, @@ -161,7 +166,7 @@ export class AutoConfirmPolicyDialogComponent } private buildMultiStepSubmit(singleOrgPolicyEnabled: boolean): Observable { - return this.managePolicies$.pipe( + return this.managePoliciesOnly$.pipe( map((managePoliciesOnly) => { const submitSteps = [ { @@ -206,6 +211,17 @@ export class AutoConfirmPolicyDialogComponent autoConfirmRequest, ); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + + const currentAutoConfirmState = await firstValueFrom( + this.autoConfirmService.configuration$(userId), + ); + + await this.autoConfirmService.upsert(userId, { + ...currentAutoConfirmState, + showSetupDialog: false, + }); + this.toastService.showToast({ variant: "success", message: this.i18nService.t("editedPolicyId", this.i18nService.t(this.data.policy.name)), diff --git a/apps/web/src/app/admin-console/organizations/policies/index.ts b/apps/web/src/app/admin-console/organizations/policies/index.ts index 624e5132faf..3042be240f7 100644 --- a/apps/web/src/app/admin-console/organizations/policies/index.ts +++ b/apps/web/src/app/admin-console/organizations/policies/index.ts @@ -2,3 +2,6 @@ export { PoliciesComponent } from "./policies.component"; export { ossPolicyEditRegister } from "./policy-edit-register"; export { BasePolicyEditDefinition, BasePolicyEditComponent } from "./base-policy-edit.component"; export { POLICY_EDIT_REGISTER } from "./policy-register-token"; +export { AutoConfirmPolicyDialogComponent } from "./auto-confirm-edit-policy-dialog.component"; +export { AutoConfirmPolicy } from "./policy-edit-definitions"; +export { PolicyEditDialogResult } from "./policy-edit-dialog.component"; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html index c6a62ab2641..54f166b662e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/auto-confirm-policy.component.html @@ -47,12 +47,12 @@
      -
    1. 1. {{ "autoConfirmStep1" | i18n }}
    2. +
    3. 1. {{ "autoConfirmExtension1" | i18n }}
    4. - 2. {{ "autoConfirmStep2a" | i18n }} + 2. {{ "autoConfirmExtension2" | i18n }} - {{ "autoConfirmStep2b" | i18n }} + {{ "autoConfirmExtension3" | i18n }}
    diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 72a563a77a3..bf741132b00 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -9,6 +9,10 @@ import { DefaultCollectionAdminService, OrganizationUserApiService, CollectionService, + AutomaticUserConfirmationService, + DefaultAutomaticUserConfirmationService, + OrganizationUserService, + DefaultOrganizationUserService, } from "@bitwarden/admin-console/common"; import { DefaultDeviceManagementComponentService } from "@bitwarden/angular/auth/device-management/default-device-management-component.service"; import { DeviceManagementComponentServiceAbstraction } from "@bitwarden/angular/auth/device-management/device-management-component.service.abstraction"; @@ -44,7 +48,10 @@ import { } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + InternalOrganizationServiceAbstraction, + OrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService, @@ -338,6 +345,29 @@ const safeProviders: SafeProvider[] = [ OrganizationService, ], }), + safeProvider({ + provide: OrganizationUserService, + useClass: DefaultOrganizationUserService, + deps: [ + KeyServiceAbstraction, + EncryptService, + OrganizationUserApiService, + AccountService, + I18nServiceAbstraction, + ], + }), + safeProvider({ + provide: AutomaticUserConfirmationService, + useClass: DefaultAutomaticUserConfirmationService, + deps: [ + ConfigService, + ApiService, + OrganizationUserService, + StateProvider, + InternalOrganizationServiceAbstraction, + OrganizationUserApiService, + ], + }), safeProvider({ provide: SdkLoadService, useClass: flagEnabled("sdk") ? WebSdkLoadService : NoopSdkLoadService, diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 7bdd290336d..4c23119f1eb 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -9,6 +9,7 @@ import { lastValueFrom, Observable, Subject, + zip, } from "rxjs"; import { concatMap, @@ -25,6 +26,7 @@ import { } from "rxjs/operators"; import { + AutomaticUserConfirmationService, CollectionData, CollectionDetailsResponse, CollectionService, @@ -54,7 +56,9 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -102,6 +106,11 @@ import { getNestedCollectionTree, getFlatCollectionTree, } from "../../admin-console/organizations/collections"; +import { + AutoConfirmPolicy, + AutoConfirmPolicyDialogComponent, + PolicyEditDialogResult, +} from "../../admin-console/organizations/policies"; import { CollectionDialogAction, CollectionDialogTabType, @@ -213,6 +222,8 @@ export class VaultComponent implements OnInit, OnDestr private destroy$ = new Subject(); private vaultItemDialogRef?: DialogRef | undefined; + private autoConfirmDialogRef?: DialogRef | undefined; + protected showAddCipherBtn: boolean = false; organizations$ = this.accountService.activeAccount$ @@ -328,6 +339,8 @@ export class VaultComponent implements OnInit, OnDestr private policyService: PolicyService, private unifiedUpgradePromptService: UnifiedUpgradePromptService, private premiumUpgradePromptService: PremiumUpgradePromptService, + private autoConfirmService: AutomaticUserConfirmationService, + private configService: ConfigService, ) {} async ngOnInit() { @@ -629,6 +642,8 @@ export class VaultComponent implements OnInit, OnDestr }, ); void this.unifiedUpgradePromptService.displayUpgradePromptConditionally(); + + this.setupAutoConfirm(); } ngOnDestroy() { @@ -1547,6 +1562,72 @@ export class VaultComponent implements OnInit, OnDestr const cipherView = await this.cipherService.decrypt(_cipher, activeUserId); return cipherView.login?.password; } + + private async openAutoConfirmFeatureDialog(organization: Organization) { + if (this.autoConfirmDialogRef) { + return; + } + + this.autoConfirmDialogRef = AutoConfirmPolicyDialogComponent.open(this.dialogService, { + data: { + policy: new AutoConfirmPolicy(), + organizationId: organization.id, + firstTimeDialog: true, + }, + }); + + await lastValueFrom(this.autoConfirmDialogRef.closed); + this.autoConfirmDialogRef = undefined; + } + + private setupAutoConfirm() { + // if the policy is enabled, then the user may only belong to one organization at most. + const organization$ = this.organizations$.pipe(map((organizations) => organizations[0])); + + const featureFlag$ = this.configService.getFeatureFlag$(FeatureFlag.AutoConfirm); + + const autoConfirmState$ = this.userId$.pipe( + switchMap((userId) => this.autoConfirmService.configuration$(userId)), + ); + + const policyEnabled$ = combineLatest([ + this.userId$.pipe( + switchMap((userId) => this.policyService.policies$(userId)), + map((policies) => policies.find((p) => p.type === PolicyType.AutoConfirm && p.enabled)), + ), + organization$, + ]).pipe( + map( + ([policy, organization]) => (policy && policy.organizationId === organization?.id) ?? false, + ), + ); + + zip([organization$, featureFlag$, autoConfirmState$, policyEnabled$, this.userId$]) + .pipe( + first(), + switchMap(async ([organization, flagEnabled, autoConfirmState, policyEnabled, userId]) => { + const showDialog = + flagEnabled && + !policyEnabled && + autoConfirmState.showSetupDialog && + !!organization && + (organization.canManageUsers || organization.canManagePolicies); + + if (showDialog) { + await this.openAutoConfirmFeatureDialog(organization); + + await this.autoConfirmService.upsert(userId, { + ...autoConfirmState, + showSetupDialog: false, + }); + } + }), + takeUntil(this.destroy$), + ) + .subscribe({ + error: (err: unknown) => this.logService.error("Failed to update auto-confirm state", err), + }); + } } /** diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 56332e5ac50..5c712c98e0d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5832,16 +5832,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." diff --git a/libs/admin-console/src/common/auto-confirm/models/auto-confirm-state.model.ts b/libs/admin-console/src/common/auto-confirm/models/auto-confirm-state.model.ts index c69db69746c..fd3cfa2f590 100644 --- a/libs/admin-console/src/common/auto-confirm/models/auto-confirm-state.model.ts +++ b/libs/admin-console/src/common/auto-confirm/models/auto-confirm-state.model.ts @@ -16,6 +16,6 @@ export const AUTO_CONFIRM_STATE = UserKeyDefinition.record( "autoConfirm", { deserializer: (autoConfirmState) => autoConfirmState, - clearOn: ["logout"], + clearOn: [], }, ); diff --git a/libs/common/src/admin-console/services/policy/default-policy.service.ts b/libs/common/src/admin-console/services/policy/default-policy.service.ts index 1107e88e796..b9d7655195b 100644 --- a/libs/common/src/admin-console/services/policy/default-policy.service.ts +++ b/libs/common/src/admin-console/services/policy/default-policy.service.ts @@ -285,6 +285,8 @@ export class DefaultPolicyService implements PolicyService { case PolicyType.RemoveUnlockWithPin: // Remove Unlock with PIN policy return false; + case PolicyType.AutoConfirm: + return false; case PolicyType.OrganizationDataOwnership: // organization data ownership policy applies to everyone except admins and owners return organization.isAdmin; diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 42d7f5aaaf8..7b1d75b2985 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -36,7 +36,7 @@ export const DELETE_MANAGED_USER_WARNING = new StateDefinition( web: "disk-local", }, ); -export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk"); +export const AUTO_CONFIRM = new StateDefinition("autoConfirm", "disk", { web: "disk-local" }); // Billing export const BILLING_DISK = new StateDefinition("billing", "disk"); From 275c6a93b4c74926a1d34e03e0a31f1e4c021bc0 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 08:40:12 +0100 Subject: [PATCH 066/249] Autosync the updated translations (#17271) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 43 +++- apps/browser/src/_locales/az/messages.json | 43 +++- apps/browser/src/_locales/be/messages.json | 43 +++- apps/browser/src/_locales/bg/messages.json | 43 +++- apps/browser/src/_locales/bn/messages.json | 43 +++- apps/browser/src/_locales/bs/messages.json | 43 +++- apps/browser/src/_locales/ca/messages.json | 43 +++- apps/browser/src/_locales/cs/messages.json | 43 +++- apps/browser/src/_locales/cy/messages.json | 43 +++- apps/browser/src/_locales/da/messages.json | 43 +++- apps/browser/src/_locales/de/messages.json | 71 +++++-- apps/browser/src/_locales/el/messages.json | 43 +++- apps/browser/src/_locales/en_GB/messages.json | 43 +++- apps/browser/src/_locales/en_IN/messages.json | 43 +++- apps/browser/src/_locales/es/messages.json | 43 +++- apps/browser/src/_locales/et/messages.json | 43 +++- apps/browser/src/_locales/eu/messages.json | 43 +++- apps/browser/src/_locales/fa/messages.json | 43 +++- apps/browser/src/_locales/fi/messages.json | 83 +++++--- apps/browser/src/_locales/fil/messages.json | 43 +++- apps/browser/src/_locales/fr/messages.json | 43 +++- apps/browser/src/_locales/gl/messages.json | 43 +++- apps/browser/src/_locales/he/messages.json | 169 +++++++++------- apps/browser/src/_locales/hi/messages.json | 43 +++- apps/browser/src/_locales/hr/messages.json | 77 ++++--- apps/browser/src/_locales/hu/messages.json | 45 ++++- apps/browser/src/_locales/id/messages.json | 43 +++- apps/browser/src/_locales/it/messages.json | 43 +++- apps/browser/src/_locales/ja/messages.json | 43 +++- apps/browser/src/_locales/ka/messages.json | 43 +++- apps/browser/src/_locales/km/messages.json | 43 +++- apps/browser/src/_locales/kn/messages.json | 43 +++- apps/browser/src/_locales/ko/messages.json | 43 +++- apps/browser/src/_locales/lt/messages.json | 43 +++- apps/browser/src/_locales/lv/messages.json | 43 +++- apps/browser/src/_locales/ml/messages.json | 43 +++- apps/browser/src/_locales/mr/messages.json | 43 +++- apps/browser/src/_locales/my/messages.json | 43 +++- apps/browser/src/_locales/nb/messages.json | 43 +++- apps/browser/src/_locales/ne/messages.json | 43 +++- apps/browser/src/_locales/nl/messages.json | 43 +++- apps/browser/src/_locales/nn/messages.json | 43 +++- apps/browser/src/_locales/or/messages.json | 43 +++- apps/browser/src/_locales/pl/messages.json | 43 +++- apps/browser/src/_locales/pt_BR/messages.json | 189 ++++++++++-------- apps/browser/src/_locales/pt_PT/messages.json | 43 +++- apps/browser/src/_locales/ro/messages.json | 43 +++- apps/browser/src/_locales/ru/messages.json | 43 +++- apps/browser/src/_locales/si/messages.json | 43 +++- apps/browser/src/_locales/sk/messages.json | 43 +++- apps/browser/src/_locales/sl/messages.json | 43 +++- apps/browser/src/_locales/sr/messages.json | 43 +++- apps/browser/src/_locales/sv/messages.json | 81 +++++--- apps/browser/src/_locales/ta/messages.json | 43 +++- apps/browser/src/_locales/te/messages.json | 43 +++- apps/browser/src/_locales/th/messages.json | 43 +++- apps/browser/src/_locales/tr/messages.json | 69 +++++-- apps/browser/src/_locales/uk/messages.json | 103 ++++++---- apps/browser/src/_locales/vi/messages.json | 77 ++++--- apps/browser/src/_locales/zh_CN/messages.json | 49 ++++- apps/browser/src/_locales/zh_TW/messages.json | 45 ++++- 61 files changed, 2528 insertions(+), 637 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index ad36ba5854a..053fb3b101f 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "اسأل عن القياسات الحيوية عند الإطلاق" }, - "premiumRequired": { - "message": "حساب البريميوم مطلوب" - }, - "premiumRequiredDesc": { - "message": "هذه المِيزة متاحة فقط للعضوية المميزة." - }, "authenticationTimeout": { "message": "مهلة المصادقة" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "يجب عليك إضافة رابط الخادم الأساسي أو على الأقل بيئة مخصصة." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "بيئة مخصصة" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 9a0239d2a34..68ea40b6808 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Açılışda biometrik soruşulsun" }, - "premiumRequired": { - "message": "Premium üzvlük lazımdır" - }, - "premiumRequiredDesc": { - "message": "Bu özəlliyi istifadə etmək üçün premium üzvlük lazımdır." - }, "authenticationTimeout": { "message": "Kimlik doğrulama vaxtı bitdi" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Təməl server URL-sini və ya ən azı bir özəl mühiti əlavə etməlisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-lər, HTTPS istifadə etməlidir." + }, "customEnvironment": { "message": "Özəl mühit" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "İlkin ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ ilə uyuşma aşkarlamasını göstər", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Riskli girişlərinizi güvənli hala gətirməyiniz əladır!" }, + "upgradeNow": { + "message": "İndi yüksəlt" + }, + "builtInAuthenticator": { + "message": "Daxili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvənli fayl anbarı" + }, + "emergencyAccess": { + "message": "Fövqəladə hal erişimi" + }, + "breachMonitoring": { + "message": "Pozuntu monitorinqi" + }, + "andMoreFeatures": { + "message": "Və daha çoxu!" + }, + "planDescPremium": { + "message": "Tam onlayn təhlükəsizlik" + }, + "upgradeToPremium": { + "message": "\"Premium\"a yüksəlt" + }, "settingDisabledByPolicy": { "message": "Bu ayar, təşkilatınızın siyasəti tərəfindən sıradan çıxarılıb.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 35aaddc13b2..b0735109b41 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Пытацца пра біяметрыю пры запуску" }, - "premiumRequired": { - "message": "Патрабуецца прэміяльны статус" - }, - "premiumRequiredDesc": { - "message": "Для выкарыстання гэтай функцыі патрабуецца прэміяльны статус." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Карыстальніцкае асяроддзе" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 68b962837eb..30386fe625e 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Питане за биометрични данни при пускане" }, - "premiumRequired": { - "message": "Изисква се платен абонамент" - }, - "premiumRequiredDesc": { - "message": "За да се възползвате от тази възможност, трябва да ползвате платен абонамент." - }, "authenticationTimeout": { "message": "Време на давност за удостоверяването" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Трябва да добавите или основния адрес на сървъра, или поне една специална среда." }, + "selfHostedEnvMustUseHttps": { + "message": "Адресите трябва да ползват HTTPS." + }, "customEnvironment": { "message": "Специална среда" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "По подразбиране ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показване на откритото съвпадение $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Добра работа с подсигуряването на данните за вписване в риск!" }, + "upgradeNow": { + "message": "Надграждане сега" + }, + "builtInAuthenticator": { + "message": "Вграден удостоверител" + }, + "secureFileStorage": { + "message": "Сигурно съхранение на файлове" + }, + "emergencyAccess": { + "message": "Авариен достъп" + }, + "breachMonitoring": { + "message": "Наблюдение за пробиви" + }, + "andMoreFeatures": { + "message": "И още!" + }, + "planDescPremium": { + "message": "Пълна сигурност в Интернет" + }, + "upgradeToPremium": { + "message": "Надградете до Платения план" + }, "settingDisabledByPolicy": { "message": "Тази настройка е изключена съгласно политиката на организацията Ви.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 25e37c06745..d68d19b0a05 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "প্রিমিয়াম আবশ্যক" - }, - "premiumRequiredDesc": { - "message": "এই বৈশিষ্ট্যটি ব্যবহার করতে একটি প্রিমিয়াম সদস্যতার প্রয়োজন।" - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "পছন্দসই পরিবেশ" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 566f0e7077e..74f47fac7df 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index a5e00afae0c..824f37f069e 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Demaneu dades biometriques en iniciar" }, - "premiumRequired": { - "message": "Premium requerit" - }, - "premiumRequiredDesc": { - "message": "Cal una subscripció premium per utilitzar aquesta característica." - }, "authenticationTimeout": { "message": "Temps d'espera d'autenticació" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorn personalitzat" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 5dd4a6a6efc..46f5f414a1a 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ověřit biometrické údaje při spuštění" }, - "premiumRequired": { - "message": "Je vyžadováno členství Premium" - }, - "premiumRequiredDesc": { - "message": "Pro použití této funkce je potřebné členství Premium." - }, "authenticationTimeout": { "message": "Časový limit ověření" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte přidat buď základní adresu URL serveru nebo alespoň jedno vlastní prostředí." }, + "selfHostedEnvMustUseHttps": { + "message": "URL adresy musí používat HTTPS." + }, "customEnvironment": { "message": "Vlastní prostředí" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Výchozí ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Zobrazit detekci shody $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Skvělá práce při zabezpečení přihlašovacích údajů v ohrožení!" }, + "upgradeNow": { + "message": "Aktualizovat nyní" + }, + "builtInAuthenticator": { + "message": "Vestavěný autentifikátor" + }, + "secureFileStorage": { + "message": "Zabezpečené úložiště souborů" + }, + "emergencyAccess": { + "message": "Nouzový přístup" + }, + "breachMonitoring": { + "message": "Sledování úniků" + }, + "andMoreFeatures": { + "message": "A ještě více!" + }, + "planDescPremium": { + "message": "Dokončit online zabezpečení" + }, + "upgradeToPremium": { + "message": "Aktualizovat na Premium" + }, "settingDisabledByPolicy": { "message": "Toto nastavení je zakázáno zásadami Vaší organizace.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 1f46f034f5e..07c5a68e3ec 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Mae angen aelodaeth uwch" - }, - "premiumRequiredDesc": { - "message": "Mae angen aelodaeth uwch i ddefnyddio'r nodwedd hon." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Amgylchedd addasedig" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 7e1f66478cf..93b311e158b 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Bed om biometri ved start" }, - "premiumRequired": { - "message": "Premium påkrævet" - }, - "premiumRequiredDesc": { - "message": "Premium-medlemskab kræves for at anvende denne funktion." - }, "authenticationTimeout": { "message": "Godkendelsestimeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Der skal tilføjes enten basis server-URL'en eller mindst ét tilpasset miljø." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Brugerdefineret miljø" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Vis matchdetektion $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 9527c15e6a3..d88c396bb80 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -32,7 +32,7 @@ "message": "Single Sign-on verwenden" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Deine Organisation erfordert Single Sign-On." }, "welcomeBack": { "message": "Willkommen zurück" @@ -592,7 +592,7 @@ "message": "Anzeigen" }, "viewAll": { - "message": "View all" + "message": "Alles anzeigen" }, "viewLogin": { "message": "Zugangsdaten anzeigen" @@ -1035,10 +1035,10 @@ "message": "Eintrag gespeichert" }, "savedWebsite": { - "message": "Saved website" + "message": "Website gespeichert" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Gespeicherte Websites ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Beim Start nach biometrischen Daten fragen" }, - "premiumRequired": { - "message": "Premium-Mitgliedschaft benötigt" - }, - "premiumRequiredDesc": { - "message": "Eine Premium-Mitgliedschaft ist für diese Funktion notwendig." - }, "authenticationTimeout": { "message": "Authentifizierungs-Timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Du musst entweder die Basis-Server-URL oder mindestens eine benutzerdefinierte Umgebung hinzufügen." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs müssen HTTPS verwenden." + }, "customEnvironment": { "message": "Benutzerdefinierte Umgebung" }, @@ -1695,28 +1692,28 @@ "message": "Auto-Ausfüllen deaktivieren" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Auto-Ausfüllen bestätigen" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Diese Website stimmt nicht mit deinen gespeicherten Zugangsdaten überein. Bevor du deine Zugangsdaten eingibst, stelle sicher, dass es sich um eine vertrauenswürdige Website handelt." }, "showInlineMenuLabel": { "message": "Vorschläge zum Auto-Ausfüllen in Formularfeldern anzeigen" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Wie schützt Bitwarden deine Daten vor Phishing?" }, "currentWebsite": { - "message": "Current website" + "message": "Aktuelle Website" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Auto-Ausfüllen und diese Website hinzufügen" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Auto-Ausfüllen ohne Hinzufügen" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Nicht automatisch ausfüllen" }, "showInlineMenuIdentitiesLabel": { "message": "Identitäten als Vorschläge anzeigen" @@ -3280,7 +3277,7 @@ "message": "Entschlüsselungsfehler" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Fehler beim Abrufen der Auto-Ausfüllen-Daten" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden konnte folgende(n) Tresor-Eintrag/Einträge nicht entschlüsseln." @@ -4054,10 +4051,10 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Kein Auto-Ausfüllen möglich" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Die Standard-Übereinstimmungserkennung steht auf \"Exakte Übereinstimmung\". Die aktuelle Website stimmt nicht genau mit den gespeicherten Zugangsdaten für diesen Eintrag überein." }, "okay": { "message": "Okay" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standard ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Übereinstimmungs-Erkennung anzeigen $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Gute Arbeit! Du hast deine gefährdeten Zugangsdaten geschützt!" }, + "upgradeNow": { + "message": "Jetzt upgraden" + }, + "builtInAuthenticator": { + "message": "Integrierter Authenticator" + }, + "secureFileStorage": { + "message": "Sicherer Dateispeicher" + }, + "emergencyAccess": { + "message": "Notfallzugriff" + }, + "breachMonitoring": { + "message": "Datenpannen-Überwachung" + }, + "andMoreFeatures": { + "message": "Und mehr!" + }, + "planDescPremium": { + "message": "Umfassende Online-Sicherheit" + }, + "upgradeToPremium": { + "message": "Upgrade auf Premium" + }, "settingDisabledByPolicy": { "message": "Diese Einstellung ist durch die Richtlinien deiner Organisation deaktiviert.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 230f5d60423..7fb60530511 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ζητήστε βιομετρικά κατά την εκκίνηση" }, - "premiumRequired": { - "message": "Απαιτείται το Premium" - }, - "premiumRequiredDesc": { - "message": "Για να χρησιμοποιήσετε αυτή τη λειτουργία, απαιτείται συνδρομή Premium." - }, "authenticationTimeout": { "message": "Χρονικό όριο επαλήθευσης" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Πρέπει να προσθέσετε είτε το βασικό URL του διακομιστή ή τουλάχιστον ένα προσαρμοσμένο περιβάλλον." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Προσαρμοσμένο περιβάλλον" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Εμφάνιση ανιχνεύσεων αντιστοίχισης $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 2058d68c55b..8ab541c569e 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 6c1b1e01139..68bf5497e37 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 1284563a6e3..060da79a4ff 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir datos biométricos al ejecutar" }, - "premiumRequired": { - "message": "Premium requerido" - }, - "premiumRequiredDesc": { - "message": "Una membrasía Premium es requerida para utilizar esta característica." - }, "authenticationTimeout": { "message": "Tiempo de autenticación agotado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes añadir la dirección URL del servidor base o al menos un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 3f163506214..acb440b2aa6 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Küsi avamisel biomeetriat" }, - "premiumRequired": { - "message": "Vajalik on Premium versioon" - }, - "premiumRequiredDesc": { - "message": "Selle funktsiooni kasutamiseks on vajalik tasulist kontot omada." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kohandatud keskkond" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index f74233193ef..016381e17f8 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Biometria eskatu saioa hastean" }, - "premiumRequired": { - "message": "Premium izatea beharrezkoa da" - }, - "premiumRequiredDesc": { - "message": "Premium bazkidetza beharrezkoa da ezaugarri hau erabiltzeko." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ingurune pertsonalizatua" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 6b52f1d4364..4f8529b2710 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "درخواست بیومتریک هنگام راه‌اندازی" }, - "premiumRequired": { - "message": "در نسخه پرمیوم کار می‌کند" - }, - "premiumRequiredDesc": { - "message": "برای استفاده از این ویژگی عضویت پرمیوم لازم است." - }, "authenticationTimeout": { "message": "پایان زمان احراز هویت" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "شما باید یا نشانی اینترنتی پایه سرور را اضافه کنید، یا حداقل یک محیط سفارشی تعریف کنید." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "محیط سفارشی" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "نمایش شناسایی تطابق برای $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index a0e6fce06bd..e3a5b44ea91 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -554,21 +554,21 @@ "message": "Nollaa haku" }, "archiveNoun": { - "message": "Archive", + "message": "Arkistoi", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "Arkistoi", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "Poista arkistosta" }, "itemsInArchive": { - "message": "Items in archive" + "message": "Arkistossa olevat kohteet" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "Arkistossa ei ole kohteita" }, "noItemsInArchiveDesc": { "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." @@ -580,7 +580,7 @@ "message": "Item was unarchived" }, "archiveItem": { - "message": "Archive item" + "message": "Arkistoi kohde" }, "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" @@ -592,7 +592,7 @@ "message": "Näytä" }, "viewAll": { - "message": "View all" + "message": "Näytä kaikki" }, "viewLogin": { "message": "View login" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pyydä Biometristä todennusta käynnistettäessä" }, - "premiumRequired": { - "message": "Premium vaaditaan" - }, - "premiumRequiredDesc": { - "message": "Tämä ominaisuus edellyttää Premium-jäsenyyttä." - }, "authenticationTimeout": { "message": "Todennuksen aikakatkaisu" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Sinun on lisättävä joko palvelimen perusosoite tai ainakin yksi mukautettu palvelinympäristö." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mukautettu palvelinympäristö" }, @@ -2009,11 +2006,11 @@ "message": "Muistiinpano" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "Uusi kirjautumistieto", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "Uusi kortti", "description": "Header for new card item type" }, "newItemHeaderIdentity": { @@ -2025,23 +2022,23 @@ "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "Uusi SSH-avain", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "Uusi teksti-Send", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "Uusi tiedosto-Send", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "Muokkaa kirjautumistietoa", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "Muokkaa korttia", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { @@ -2053,23 +2050,23 @@ "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "Muokkaa SSH avainta", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Muokkaa teksti-Sendiä", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "Muokkaa tiedosto-Sendiä", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "Näytä kirjautumistieto", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "Näytä kortti", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { @@ -2081,7 +2078,7 @@ "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "Näytä SSH-avain", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Näytä vastaavuuden tunnistus $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 3c249b0a350..2b58095d950 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Mangyaring humingi ng mga biometrika sa paglunsad" }, - "premiumRequired": { - "message": "Premium na kinakailangan" - }, - "premiumRequiredDesc": { - "message": "Ang Premium na membership ay kinakailangan upang gamitin ang tampok na ito." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Kapaligirang Custom" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index e3a153fe34f..afb58afcc25 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Demander la biométrie au lancement" }, - "premiumRequired": { - "message": "Premium requis" - }, - "premiumRequiredDesc": { - "message": "Une adhésion Premium est requise pour utiliser cette fonctionnalité." - }, "authenticationTimeout": { "message": "Délai d'authentification dépassé" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Vous devez ajouter soit l'URL du serveur de base, soit au moins un environnement personnalisé." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Environnement personnalisé" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Afficher la détection de correspondance $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Excellent travail pour sécuriser vos identifiants à risque !" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "Ce paramètre est désactivé par la politique de sécurité de votre organisation.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 6a13ce033b1..6d0410f112c 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Requirir biometría no inicio" }, - "premiumRequired": { - "message": "Plan Prémium requirido" - }, - "premiumRequiredDesc": { - "message": "Requírese un plan Prémium para poder empregar esta función." - }, "authenticationTimeout": { "message": "Tempo límite de autenticación superado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Debes engadir ou a URL base do servidor ou, polo menos, un entorno personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Entorno personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar detección de coincidencia $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 3834745f8e9..cc78e1a154a 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -32,7 +32,7 @@ "message": "השתמש בכניסה יחידה" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "הארגון שלך דורש כניסה יחידה." }, "welcomeBack": { "message": "ברוך שובך" @@ -554,36 +554,36 @@ "message": "אפס חיפוש" }, "archiveNoun": { - "message": "Archive", + "message": "ארכיון", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "העבר לארכיון", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "הסר מהארכיון" }, "itemsInArchive": { - "message": "Items in archive" + "message": "פריטים בארכיון" }, "noItemsInArchive": { - "message": "No items in archive" + "message": "אין פריטים בארכיון" }, "noItemsInArchiveDesc": { - "message": "Archived items will appear here and will be excluded from general search results and autofill suggestions." + "message": "פריטים בארכיון יופיעו כאן ויוחרגו מתוצאות חיפוש כללי והצעות למילוי אוטומטי." }, "itemWasSentToArchive": { - "message": "Item was sent to archive" + "message": "הפריט נשלח לארכיון" }, "itemUnarchived": { - "message": "Item was unarchived" + "message": "הפריט הוסר מהארכיון" }, "archiveItem": { - "message": "Archive item" + "message": "העבר פריט לארכיון" }, "archiveItemConfirmDesc": { - "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" + "message": "פריטים בארכיון מוחרגים מתוצאות חיפוש כללי והצעות למילוי אוטומטי. האם אתה בטוח שברצונך להעביר פריט זה לארכיון?" }, "edit": { "message": "ערוך" @@ -592,7 +592,7 @@ "message": "הצג" }, "viewAll": { - "message": "View all" + "message": "הצג הכל" }, "viewLogin": { "message": "הצג כניסה" @@ -740,7 +740,7 @@ "message": "סיסמה ראשית שגויה" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.", + "message": "סיסמה ראשית אינה תקינה. יש לאשר שהדוא\"ל שלך נכון ושהחשבון שלך נוצר ב־$HOST$.", "placeholders": { "host": { "content": "$1", @@ -1035,10 +1035,10 @@ "message": "הפריט נשמר" }, "savedWebsite": { - "message": "Saved website" + "message": "אתר אינטרנט שנשמר" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "אתרי אינטרנט שנשמרו ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "בקש זיהוי ביומטרי בפתיחה" }, - "premiumRequired": { - "message": "נדרש פרימיום" - }, - "premiumRequiredDesc": { - "message": "נדרשת חברות פרימיום כדי להשתמש בתכונה זו." - }, "authenticationTimeout": { "message": "פסק זמן לאימות" }, @@ -1567,13 +1561,13 @@ "message": "קרא מפתח אבטחה" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "קורא מפתח גישה..." }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "אימות מפתח גישה נכשל" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "השתמש בשיטת כניסה אחרת" }, "awaitingSecurityKeyInteraction": { "message": "ממתין לאינטראקציה עם מפתח אבטחה..." @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "אתה מוכרח להוסיף או את בסיס ה־URL של השרת או לפחות סביבה מותאמת אישית אחת." }, + "selfHostedEnvMustUseHttps": { + "message": "כתובות URL מוכרחות להשתמש ב־HTTPS." + }, "customEnvironment": { "message": "סביבה מותאמת אישית" }, @@ -1695,28 +1692,28 @@ "message": "השבת מילוי אוטומטי" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "אשר מילוי אוטומטי" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "אתר זה אינו תואם את פרטי הכניסה השמורה שלך. לפני שאתה ממלא את אישורי הכניסה שלך, וודא שזהו אתר מהימן." }, "showInlineMenuLabel": { "message": "הצג הצעות למילוי אוטומטי על שדות טופס" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "כיצד Bitwarden מגנה על הנתונים שלך מדיוג?" }, "currentWebsite": { - "message": "Current website" + "message": "אתר נוכחי" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "מלא אוטומטית והוסף אתר אינטרנט זה" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "מלא אוטומטית מבלי להוסיף" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "אל תמלא אוטומטית" }, "showInlineMenuIdentitiesLabel": { "message": "הצג זהויות כהצעות" @@ -2009,79 +2006,79 @@ "message": "הערה" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "כניסה חדשה", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "כרטיס חדש", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "זהות חדשה", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "הערה חדשה", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "מפתח SSH חדש", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "סֵנְד של טקסט חדש", "description": "Header for new text send" }, "newItemHeaderFileSend": { - "message": "New File Send", + "message": "סֵנְד של קובץ חדש", "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "ערוך כניסה", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "ערוך כרטיס", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { - "message": "Edit Identity", + "message": "ערוך זהות", "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "ערוך הערה", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "ערוך מפתח SSH", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "ערוך סֵנְד של טקסט", "description": "Header for edit text send" }, "editItemHeaderFileSend": { - "message": "Edit File Send", + "message": "ערוך סֵנְד של קובץ", "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "הצג כניסה", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "הצג כרטיס", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { - "message": "View Identity", + "message": "הצג זהות", "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "הצג הערה", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "הצג מפתח SSH", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -3256,7 +3253,7 @@ } }, "exportingOrganizationVaultFromPasswordManagerWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא.", "placeholders": { "organization": { "content": "$1", @@ -3265,7 +3262,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Only the organization vault associated with $ORGANIZATION$ will be exported. My items collections will not be included.", + "message": "רק הכספת הארגונית המשויכת עם $ORGANIZATION$ תיוצא. פריטי האוספים שלי לא יכללו.", "placeholders": { "organization": { "content": "$1", @@ -3280,7 +3277,7 @@ "message": "שגיאת פענוח" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "שגיאה בקבלת נתוני מילוי אוטומטי" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden לא יכל לפענח את פריט(י) הכספת המפורט(ים) להלן." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "לא ניתן למלא אוטומטית" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "ברירת המחדל להתאמה מוגדרת כ'התאמה מדויקת'. האתר הנוכחי אינו תואם באופן מדויק את פרטי הכניסה השמורים עבור פריט זה." }, "okay": { - "message": "Okay" + "message": "בסדר" }, "toggleSideNavigation": { "message": "החלף מצב ניווט צדדי" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "הצג זיהוי התאמה $WEBSITE$", "placeholders": { @@ -5586,7 +5593,7 @@ "message": "אפשרויות כספת" }, "emptyVaultDescription": { - "message": "הכספת מגינה על יותר מרק הסיסמאות שלך. אחסן כניסות מאובטחות, זהויות, כרטיסים והערות באופן מאובטח כאן." + "message": "הכספת מגנה על יותר מרק הסיסמאות שלך. אחסן כניסות מאובטחות, זהויות, כרטיסים והערות באופן מאובטח כאן." }, "introCarouselLabel": { "message": "ברוך בואך אל Bitwarden" @@ -5631,30 +5638,30 @@ "message": "ברוך בואך אל הכספת שלך!" }, "phishingPageTitleV2": { - "message": "Phishing attempt detected" + "message": "זוהה ניסיון דיוג" }, "phishingPageSummary": { - "message": "The site you are attempting to visit is a known malicious site and a security risk." + "message": "האתר שאתה מנסה לבקר הוא אתר זדוני ידוע וסכנת אבטחה." }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "סגור כרטיסיה זו" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "המשך לאתר זה (לא מומלץ)" }, "phishingPageExplanation1": { - "message": "This site was found in ", + "message": "אתר זה נמצא ב־", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name follows this." }, "phishingPageExplanation2": { - "message": ", an open-source list of known phishing sites used for stealing personal and sensitive information.", + "message": ", רשימת קוד פתוח של אתרי דיוג ידועים המשמשים לגניבת מידע אישי ורגיש.", "description": "This is in multiple parts to allow for bold text in the middle of the sentence. A proper name precedes this." }, "phishingPageLearnMore": { - "message": "Learn more about phishing detection" + "message": "למד עוד על זיהוי דיוג" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "מוגן על ידי $PRODUCT$", "placeholders": { "product": { "content": "$1", @@ -5767,16 +5774,40 @@ "message": "אשר דומיין של Key Connector" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "עבודה נהדרת באבטחת הכניסות בסיכון שלך!" + }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "הגדרה זו מושבתת על ידי מדיניות של הארגון שלך.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "מיקוד" }, "cardNumberLabel": { - "message": "Card number" + "message": "מספר כרטיס" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 3172e767974..c27fa6f7eb7 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "लॉन्च पर बायोमेट्रिक्स के लिए पूछें" }, - "premiumRequired": { - "message": "Premium Required" - }, - "premiumRequiredDesc": { - "message": "इस सुविधा का उपयोग करने के लिए प्रीमियम सदस्यता की आवश्यकता होती है।" - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 9e4c8d34004..9d7539a9bd5 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -32,7 +32,7 @@ "message": "Jedinstvena prijava (SSO)" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tvoja organizacija zahtijeva jedinstvenu prijavu." }, "welcomeBack": { "message": "Dobro došli natrag" @@ -592,7 +592,7 @@ "message": "Prikaz" }, "viewAll": { - "message": "View all" + "message": "Vidi sve" }, "viewLogin": { "message": "Prikaži prijavu" @@ -1035,10 +1035,10 @@ "message": "Stavka izmijenjena" }, "savedWebsite": { - "message": "Saved website" + "message": "Spremljeno mrežno mjesto" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Spremljena mrežna mjesta ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Traži biometrijsku autentifikaciju pri pokretanju" }, - "premiumRequired": { - "message": "Potrebno premium članstvo" - }, - "premiumRequiredDesc": { - "message": "Za korištenje ove značajke potrebno je Premium članstvo." - }, "authenticationTimeout": { "message": "Istek vremena za autentifikaciju" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Moraš dodati ili osnovni URL poslužitelja ili barem jedno prilagođeno okruženje." }, + "selfHostedEnvMustUseHttps": { + "message": "URL mora koristiti HTTPS." + }, "customEnvironment": { "message": "Prilagođeno okruženje" }, @@ -1695,28 +1692,28 @@ "message": "Isključi auto-ispunu" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Potvrdi auto-ispunu" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Ova stranica ne odgovara tvojim spremljenim podacima za prijavu. Prije nego što uneseš svoje podatke za prijavu, provjeri je li riječ o pouzdanoj stranici." }, "showInlineMenuLabel": { "message": "Prikaži prijedloge auto-ispune na poljima obrazaca" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Kako Bitwarden štiti tvoje podatke od phishinga?" }, "currentWebsite": { - "message": "Current website" + "message": "Trenutna web stranica" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Auto-ispuni i dodaj ovu stranicu" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Auto-ispuni bez dodavanja" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Nemoj auto-ispuniti" }, "showInlineMenuIdentitiesLabel": { "message": "Prikaži identitete kao prijedloge" @@ -3280,7 +3277,7 @@ "message": "Pogreška pri dešifriranju" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Greška kod dohvata podataka za auto-ispunu" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden nije mogao dešifrirati sljedeće stavke trezora." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Nije moguća auto-ispuna" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Zadano podudaranje postavljeno je na „Točno podudaranje”. Trenutna web-stranica ne podudara se točno sa spremljenim podacima ove stavke za prijavu." }, "okay": { - "message": "Okay" + "message": "U redu" }, "toggleSideNavigation": { "message": "U/Isključi bočnu navigaciju" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Zadano ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Prikaži otkrivanje podudaranja $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Rizične prijave su osigurane!" }, + "upgradeNow": { + "message": "Nadogradi sada" + }, + "builtInAuthenticator": { + "message": "Ugrađeni autentifikator" + }, + "secureFileStorage": { + "message": "Sigurna pohrana datoteka" + }, + "emergencyAccess": { + "message": "Pristup u nuždi" + }, + "breachMonitoring": { + "message": "Nadzor proboja" + }, + "andMoreFeatures": { + "message": "I više!" + }, + "planDescPremium": { + "message": "Dovrši online sigurnost" + }, + "upgradeToPremium": { + "message": " Nadogradi na Premium" + }, "settingDisabledByPolicy": { "message": "Ova je postavka onemogućena pravilima tvoje organizacije.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Poštanski broj" }, "cardNumberLabel": { - "message": "Card number" + "message": "Broj kartice" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index a84487e5a1d..9f10494258a 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -32,7 +32,7 @@ "message": "Egyszeri bejelentkezés használata" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A szervezet egyszeri bejelentkezést igényel." }, "welcomeBack": { "message": "Üdvözlet újra" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Biometria kérése indításkor" }, - "premiumRequired": { - "message": "Prémium funkció szükséges" - }, - "premiumRequiredDesc": { - "message": "Prémium tagság szükséges ennek a funkciónak eléréséhez a jövőben." - }, "authenticationTimeout": { "message": "Hitelesítési időkifutás" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Hozzá kell adni az alapszerver webcímét vagy legalább egy egyedi környezetet." }, + "selfHostedEnvMustUseHttps": { + "message": "A webcímeknek HTTPS-t kell használniuk." + }, "customEnvironment": { "message": "Egyedi környezet" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ egyező érzékelés megjelenítése", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Remek munka a kockázatos bejelentkezések biztosítása!" }, + "upgradeNow": { + "message": "Áttérés most" + }, + "builtInAuthenticator": { + "message": "Beépített hitelesítő" + }, + "secureFileStorage": { + "message": "Biztonságos fájl tárolás" + }, + "emergencyAccess": { + "message": "Sürgősségi hozzáférés" + }, + "breachMonitoring": { + "message": "Adatszivárgás figyelés" + }, + "andMoreFeatures": { + "message": "És még több!" + }, + "planDescPremium": { + "message": "Teljes körű online biztonság" + }, + "upgradeToPremium": { + "message": "Áttérés Prémium csomagra" + }, "settingDisabledByPolicy": { "message": "Ezt a beállítást a szervezet házirendje letiltotta.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index a94709a1be1..26a2b8dc6bd 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Tanyakan untuk biometrik pada saat diluncurkan" }, - "premiumRequired": { - "message": "Membutuhkan Keanggotaan Premium" - }, - "premiumRequiredDesc": { - "message": "Keanggotaan premium diperlukan untuk menggunakan fitur ini." - }, "authenticationTimeout": { "message": "Batas waktu otentikasi" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Anda harus menambahkan antara URL dasar server atau paling tidak satu lingkungan ubahsuai." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Lingkungan Khusus" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Tampilkan deteksi kecocokan $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 05cd6937246..60c97d7157a 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Richiedi dati biometrici all'avvio" }, - "premiumRequired": { - "message": "Premium necessario" - }, - "premiumRequiredDesc": { - "message": "Passa a Premium per utilizzare questa funzionalità." - }, "authenticationTimeout": { "message": "Timeout autenticazione" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Devi aggiungere lo URL del server di base o almeno un ambiente personalizzato." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizzato" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostra corrispondenza $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "Questa impostazione è disabilitata dalle restrizioni della tua organizzazione.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 54405f69157..7c4420508a2 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "起動時に生体認証を要求する" }, - "premiumRequired": { - "message": "プレミアム会員専用" - }, - "premiumRequiredDesc": { - "message": "この機能を使うにはプレミアム会員になってください。" - }, "authenticationTimeout": { "message": "認証のタイムアウト" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "カスタム環境" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "一致検出 $WEBSITE$を表示", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 82f18caf79f..eaa5bc43021 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index f1c9e0ee8ab..e5adcfce833 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "ಪ್ರೀಮಿಯಂ ಅಗತ್ಯವಿದೆ" - }, - "premiumRequiredDesc": { - "message": "ಈ ವೈಶಿಷ್ಟ್ಯವನ್ನು ಬಳಸಲು ಪ್ರೀಮಿಯಂ ಸದಸ್ಯತ್ವ ಅಗತ್ಯವಿದೆ." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ಕಸ್ಟಮ್ ಪರಿಸರ" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index c5a414fc81f..6037d208b42 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "실행 시 생체 인증 요구하기" }, - "premiumRequired": { - "message": "프리미엄 멤버십 필요" - }, - "premiumRequiredDesc": { - "message": "이 기능을 사용하려면 프리미엄 멤버십이 필요합니다." - }, "authenticationTimeout": { "message": "인증 시간 초과" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "기본 서버 URL이나 최소한 하나의 사용자 지정 환경을 추가해야 합니다." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "사용자 지정 환경" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ 일치 인식 보이기", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index df55af589bf..8e858de4f47 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Paleidžiant patvirtinti biometrinius duomenis" }, - "premiumRequired": { - "message": "Premium reikalinga" - }, - "premiumRequiredDesc": { - "message": "Premium narystė reikalinga šiai funkcijai naudoti." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Individualizuota aplinka" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 6ca99492c50..a4be22d433a 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Palaižot vaicāt biometriju" }, - "premiumRequired": { - "message": "Nepieciešams Premium" - }, - "premiumRequiredDesc": { - "message": "Ir nepieciešama Premium dalība, lai izmantotu šo iespēju." - }, "authenticationTimeout": { "message": "Autentificēšanās noildze" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Jāpievieno vai no servera pamata URL vai vismaz viena pielāgota vide." }, + "selfHostedEnvMustUseHttps": { + "message": "URL ir jābūt HTTPS." + }, "customEnvironment": { "message": "Pielāgota vide" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Noklusējums ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Rādīt atbilstības noteikšanu $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Labs darbs riskam pakļauto pieteikšanās vienumu drošības uzlabošanā!" }, + "upgradeNow": { + "message": "Uzlabot tagad" + }, + "builtInAuthenticator": { + "message": "Iebūvēts autentificētājs" + }, + "secureFileStorage": { + "message": "Droša datņu krātuve" + }, + "emergencyAccess": { + "message": "Ārkārtas piekļuve" + }, + "breachMonitoring": { + "message": "Noplūžu pārraudzīšana" + }, + "andMoreFeatures": { + "message": "Un vēl!" + }, + "planDescPremium": { + "message": "Pilnīga drošība tiešsaistē" + }, + "upgradeToPremium": { + "message": "Uzlabot uz Premium" + }, "settingDisabledByPolicy": { "message": "Šis iestatījums ir atspējots apvienības pamatnostādnēs.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index 75eeb54c176..cda9ec03923 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "പ്രീമിയം അംഗത്വം ആവശ്യമാണ്" - }, - "premiumRequiredDesc": { - "message": "ഈ സവിശേഷത ഉപയോഗിക്കുന്നതിന് പ്രീമിയം അംഗത്വം ആവശ്യമാണ്." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "ഇഷ്‌ടാനുസൃത എൻവിയോണ്മെന്റ്" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 333dda2a2f8..57624a82381 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 3d632a60d3c..1268c960c8f 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Spør om biometri ved oppstart" }, - "premiumRequired": { - "message": "Premium er påkrevd" - }, - "premiumRequiredDesc": { - "message": "Et Premium-medlemskap er påkrevd for å bruke denne funksjonen." - }, "authenticationTimeout": { "message": "Tidsavbrudd for autentisering" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Tilpasset miljø" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index d413149bd18..441ea71d840 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Vraag om biometrie bij opstarten" }, - "premiumRequired": { - "message": "Premium is vereist" - }, - "premiumRequiredDesc": { - "message": "Je hebt een Premium-abonnement nodig om deze functie te gebruiken." - }, "authenticationTimeout": { "message": "Authenticatie-timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Je moet de basis URL van de server of ten minste één aangepaste omgeving toevoegen." }, + "selfHostedEnvMustUseHttps": { + "message": "URL's moeten HTTPS gebruiken." + }, "customEnvironment": { "message": "Aangepaste omgeving" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standaard ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Overeenkomstdetectie weergeven $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Goed gedaan, je hebt je risicovolle inloggegevens verbeterd!" }, + "upgradeNow": { + "message": "Nu upgraden" + }, + "builtInAuthenticator": { + "message": "Ingebouwde authenticator" + }, + "secureFileStorage": { + "message": "Beveiligde bestandsopslag" + }, + "emergencyAccess": { + "message": "Noodtoegang" + }, + "breachMonitoring": { + "message": "Lek-monitoring" + }, + "andMoreFeatures": { + "message": "En meer!" + }, + "planDescPremium": { + "message": "Online beveiliging voltooien" + }, + "upgradeToPremium": { + "message": "Opwaarderen naar Premium" + }, "settingDisabledByPolicy": { "message": "Deze instelling is uitgeschakeld door het beleid van uw organisatie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 6c9bea95451..1047ac9466e 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Wymagaj odblokowania biometrią po uruchomieniu przeglądarki" }, - "premiumRequired": { - "message": "Konto premium jest wymagane" - }, - "premiumRequiredDesc": { - "message": "Konto premium jest wymagane, aby skorzystać z tej funkcji." - }, "authenticationTimeout": { "message": "Przekroczono limit czasu uwierzytelniania" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musisz dodać podstawowy adres URL serwera lub co najmniej jedno niestandardowe środowisko." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Niestandardowe środowisko" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Pokaż wykrywanie dopasowania $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index 1496455e85b..a4da9025a8e 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -32,7 +32,7 @@ "message": "Usar autenticação única" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "A sua organização requer o uso da autenticação única." }, "welcomeBack": { "message": "Boas-vindas de volta" @@ -59,13 +59,13 @@ "message": "Endereço de e-mail" }, "masterPass": { - "message": "Senha mestra" + "message": "Senha principal" }, "masterPassDesc": { - "message": "A senha mestra é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha mestra. Não há maneira de recuperar a senha caso você se esqueça." + "message": "A senha principal é a senha que você usa para acessar o seu cofre. É muito importante que você não esqueça sua senha principal. Não há maneira de recuperar a senha caso você se esqueça." }, "masterPassHintDesc": { - "message": "Uma dica de senha mestra pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." + "message": "Uma dica de senha principal pode ajudá-lo(a) a lembrá-lo(a) caso você esqueça." }, "masterPassHintText": { "message": "Se você esquecer sua senha, a dica de senha pode ser enviada ao seu e-mail. $CURRENT$/$MAXIMUM$ caracteres máximos.", @@ -81,10 +81,10 @@ } }, "reTypeMasterPass": { - "message": "Digite novamente a senha mestra" + "message": "Digite novamente a senha principal" }, "masterPassHint": { - "message": "Dica de Senha Mestra (opcional)" + "message": "Dica de senha principal (opcional)" }, "passwordStrengthScore": { "message": "Pontuação de força da senha $SCORE$", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Termine de juntar-se nessa organização definindo uma senha mestra." + "message": "Termine de juntar-se à organização definindo uma senha principal." }, "tab": { "message": "Aba" @@ -264,13 +264,13 @@ "message": "Solicitar dica" }, "requestPasswordHint": { - "message": "Dica da senha mestra" + "message": "Dica da senha principal" }, "enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": { "message": "Digite o endereço de e-mail da sua conta e dica da sua senha será enviada para você" }, "getMasterPasswordHint": { - "message": "Obter dica da senha mestra" + "message": "Obter dica da senha principal" }, "continue": { "message": "Continuar" @@ -291,7 +291,7 @@ "message": "Confirme a sua identidade para continuar." }, "changeMasterPassword": { - "message": "Alterar senha mestra" + "message": "Alterar senha principal" }, "continueToWebApp": { "message": "Continuar no aplicativo web?" @@ -312,7 +312,7 @@ "message": "Ajude outras pessoas a descobrirem se o Bitwarden é o que elas estão procurando. Visite a loja de extensões do seu navegador e deixe uma avaliação agora." }, "changeMasterPasswordOnWebConfirmation": { - "message": "Você pode alterar a sua senha mestra no aplicativo web do Bitwarden." + "message": "Você pode alterar a sua senha principal no aplicativo web do Bitwarden." }, "fingerprintPhrase": { "message": "Frase biométrica", @@ -592,7 +592,7 @@ "message": "Ver" }, "viewAll": { - "message": "View all" + "message": "Ver tudo" }, "viewLogin": { "message": "Ver credencial" @@ -737,10 +737,10 @@ } }, "invalidMasterPassword": { - "message": "Senha mestra inválida" + "message": "Senha principal inválida" }, "invalidMasterPasswordConfirmEmailAndHost": { - "message": "Senha mestre inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", + "message": "Senha principal inválida. Confirme que seu e-mail está correto e sua conta foi criada em $HOST$.", "placeholders": { "host": { "content": "$1", @@ -806,16 +806,16 @@ "message": "Segurança" }, "confirmMasterPassword": { - "message": "Confirme a senha mestra" + "message": "Confirme a senha principal" }, "masterPassword": { - "message": "Senha mestra" + "message": "Senha principal" }, "masterPassImportant": { - "message": "Sua senha mestra não pode ser recuperada se você esquecê-la!" + "message": "Sua senha principal não pode ser recuperada se você esquecê-la!" }, "masterPassHintLabel": { - "message": "Dica da senha mestra" + "message": "Dica da senha principal" }, "errorOccurred": { "message": "Ocorreu um erro" @@ -827,13 +827,13 @@ "message": "Endereço de e-mail inválido." }, "masterPasswordRequired": { - "message": "A senha mestre é necessária." + "message": "A senha principal é necessária." }, "confirmMasterPasswordRequired": { - "message": "É necessário digitar a senha mestra novamente." + "message": "É necessário digitar a senha principal novamente." }, "masterPasswordMinlength": { - "message": "A senha mestra deve ter pelo menos $VALUE$ caracteres.", + "message": "A senha principal deve ter pelo menos $VALUE$ caracteres.", "description": "The Master Password must be at least a specific number of characters long.", "placeholders": { "value": { @@ -843,7 +843,7 @@ } }, "masterPassDoesntMatch": { - "message": "A confirmação da senha mestra não corresponde." + "message": "A confirmação da senha principal não corresponde." }, "newAccountCreated": { "message": "A sua nova conta foi criada! Agora você pode iniciar a sessão." @@ -861,7 +861,7 @@ "message": "Você pode fechar esta janela" }, "masterPassSent": { - "message": "Enviamos um e-mail com a dica da sua senha mestra." + "message": "Enviamos um e-mail com a dica da sua senha principal." }, "verificationCodeRequired": { "message": "O código de verificação é necessário." @@ -1035,10 +1035,10 @@ "message": "Item salvo" }, "savedWebsite": { - "message": "Saved website" + "message": "Site salvo" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Sites salvos ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1242,7 +1242,7 @@ "message": "Ao alterar sua senha, você precisará entrar com a sua senha nova. Sessões ativas em outros dispositivos serão desconectados dentro de uma hora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Mude a sua senha mestre para completar a recuperação de conta." + "message": "Mude a sua senha principal para completar a recuperação de conta." }, "enableChangedPasswordNotification": { "message": "Pedir para atualizar credencial existente" @@ -1326,7 +1326,7 @@ "message": "Esta senha será usada para exportar e importar este arquivo" }, "accountRestrictedOptionDescription": { - "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha mestra da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." + "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtectedOptionDescription": { "message": "Defina uma senha para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografar." @@ -1361,7 +1361,7 @@ "message": "As chaves de criptografia são únicas para cada conta de usuário do Bitwarden, então você não pode importar um arquivo de exportação criptografado para uma conta diferente." }, "exportMasterPassword": { - "message": "Insira a sua senha mestra para exportar os dados do seu cofre." + "message": "Insira a sua senha principal para exportar os dados do seu cofre." }, "shared": { "message": "Compartilhado" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir biometria ao abrir" }, - "premiumRequired": { - "message": "Requer Assinatura Premium" - }, - "premiumRequiredDesc": { - "message": "Uma assinatura Premium é necessária para usar esse recurso." - }, "authenticationTimeout": { "message": "Tempo de autenticação esgotado" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Você deve adicionar um URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs devem usar HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -1695,28 +1692,28 @@ "message": "Desativar o preenchimento automático" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Confirmar preenchimento automático" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Esse site não corresponde aos detalhes salvos na credencial. Antes de preencher suas credenciais de acesso, certifique-se de que é um site confiável." }, "showInlineMenuLabel": { "message": "Mostrar sugestões de preenchimento automático em campos de formulário" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Como que o Bitwarden protege seus dados de phishing?" }, "currentWebsite": { - "message": "Current website" + "message": "Site atual" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Preencher automaticamente e adicionar este site" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Preencher automaticamente sem adicionar" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Não preencher automaticamente" }, "showInlineMenuIdentitiesLabel": { "message": "Exibir identidades como sugestões" @@ -2267,10 +2264,10 @@ "description": "ex. A weak password. Scale: Weak -> Good -> Strong" }, "weakMasterPassword": { - "message": "Senha mestra fraca" + "message": "Senha principal Fraca" }, "weakMasterPasswordDesc": { - "message": "A senha mestra que você selecionou está fraca. Você deve usar uma senha mestra forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha mestra?" + "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" }, "pin": { "message": "PIN", @@ -2304,7 +2301,7 @@ "message": "Desbloquear com a biometria" }, "unlockWithMasterPassword": { - "message": "Desbloquear com senha mestra" + "message": "Desbloquear com senha principal" }, "awaitDesktop": { "message": "Aguardando confirmação do desktop" @@ -2313,10 +2310,10 @@ "message": "Confirme o uso de biometria no aplicativo do Bitwarden Desktop para ativar a biometria para o navegador." }, "lockWithMasterPassOnRestart": { - "message": "Bloquear com senha mestra ao reiniciar o navegador" + "message": "Bloquear com senha principal ao reiniciar o navegador" }, "lockWithMasterPassOnRestart1": { - "message": "Exigir senha mestra ao reiniciar o navegador" + "message": "Exigir senha principal ao reiniciar o navegador" }, "selectOneCollection": { "message": "Você deve selecionar pelo menos uma coleção." @@ -2431,19 +2428,19 @@ } }, "setMasterPassword": { - "message": "Definir senha mestra" + "message": "Definir senha principal" }, "currentMasterPass": { - "message": "Senha mestra atual" + "message": "Senha principal atual" }, "newMasterPass": { - "message": "Nova senha mestra" + "message": "Nova senha principal" }, "confirmNewMasterPass": { - "message": "Confirmar nova senha mestra" + "message": "Confirmar nova senha principal" }, "masterPasswordPolicyInEffect": { - "message": "Uma ou mais políticas da organização exigem que a sua senha mestra cumpra aos seguintes requisitos:" + "message": "Uma ou mais políticas da organização exigem que a sua senha principal cumpra aos seguintes requisitos:" }, "policyInEffectMinComplexity": { "message": "Pontuação mínima de complexidade de $SCORE$", @@ -2482,7 +2479,7 @@ } }, "masterPasswordPolicyRequirementsNotMet": { - "message": "A sua nova senha mestra não cumpre aos requisitos da política." + "message": "A sua nova senha principal não cumpre aos requisitos da política." }, "receiveMarketingEmailsV2": { "message": "Receba conselhos, novidades, e oportunidades de pesquisa do Bitwarden em sua caixa de entrada." @@ -2596,7 +2593,7 @@ "message": "Biometria falhou" }, "biometricsFailedDesc": { - "message": "A biometria não pode ser concluída, considere usar uma senha mestra ou desconectar. Se isso persistir, entre em contato com o suporte do Bitwarden." + "message": "A biometria não pode ser concluída, considere usar uma senha principal ou desconectar. Se isso persistir, entre em contato com o suporte do Bitwarden." }, "nativeMessaginPermissionErrorTitle": { "message": "Permissão não fornecida" @@ -3034,13 +3031,13 @@ "message": "Oculte seu endereço de e-mail dos visualizadores." }, "passwordPrompt": { - "message": "Solicitação nova de senha mestra" + "message": "Solicitação nova de senha principal" }, "passwordConfirmation": { - "message": "Confirmação de senha mestra" + "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha mestra para verificar sua identidade." + "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." }, "emailVerificationRequired": { "message": "Verificação de e-mail necessária" @@ -3052,28 +3049,28 @@ "message": "Você precisa verificar o seu e-mail para usar este recurso. Você pode verificar seu e-mail no cofre web." }, "masterPasswordSuccessfullySet": { - "message": "Senha mestra definida com sucesso" + "message": "Senha principal definida com sucesso" }, "updatedMasterPassword": { - "message": "Senha mestra atualizada" + "message": "Senha principal atualizada" }, "updateMasterPassword": { - "message": "Atualizar senha mestra" + "message": "Atualizar senha principal" }, "updateMasterPasswordWarning": { - "message": "Sua senha mestra foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha mestra não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha mestra agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha mestra para acessar o seu cofre." + "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha principal para acessar o seu cofre." }, "resetPasswordPolicyAutoEnroll": { "message": "Inscrição automática" }, "resetPasswordAutoEnrollInviteWarning": { - "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha mestra." + "message": "Esta organização possui uma política empresarial que irá inscrevê-lo automaticamente na redefinição de senha. A inscrição permitirá que os administradores da organização alterem sua senha principal." }, "selectFolder": { "message": "Selecionar pasta..." @@ -3083,11 +3080,11 @@ "description": "Used as a message within the notification bar when no folders are found" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha mestra.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha mestra.", + "message": "Sua organização requer que você defina uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -3193,7 +3190,7 @@ "message": "Nenhum identificador exclusivo encontrado." }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha mestra não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." + "message": "Uma senha principal não é mais necessária para os membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." }, "organizationName": { "message": "Nome da organização" @@ -3205,10 +3202,10 @@ "message": "Sair da organização" }, "removeMasterPassword": { - "message": "Remover senha mestra" + "message": "Remover senha principal" }, "removedMasterPassword": { - "message": "Senha mestra removida" + "message": "Senha principal removida" }, "leaveOrganizationConfirmation": { "message": "Você tem certeza que deseja sair desta organização?" @@ -3280,7 +3277,7 @@ "message": "Erro ao descriptografar" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Erro ao obter dados de preenchimento automático" }, "couldNotDecryptVaultItemsBelow": { "message": "O Bitwarden não conseguiu descriptografar o(s) item(ns) do cofre listado abaixo." @@ -3557,7 +3554,7 @@ } }, "loginWithMasterPassword": { - "message": "Entrar com a senha mestra" + "message": "Entrar com a senha principal" }, "newAroundHere": { "message": "Novo por aqui?" @@ -3630,16 +3627,16 @@ "message": "Estado de autenticação" }, "masterPasswordChanged": { - "message": "Senha mestre salva" + "message": "Senha principal salva" }, "exposedMasterPassword": { - "message": "Senha mestra comprometida" + "message": "Senha principal comprometida" }, "exposedMasterPasswordDesc": { "message": "A senha foi encontrada em um vazamento de dados. Use uma senha única para proteger sua conta. Tem certeza de que deseja usar uma senha já exposta?" }, "weakAndExposedMasterPassword": { - "message": "Senha mestra fraca e comprometida" + "message": "Senha principal fraca e comprometida" }, "weakAndBreachedMasterPasswordDesc": { "message": "Senha fraca identificada e encontrada em um vazamento de dados. Use uma senha forte e única para proteger a sua conta. Tem certeza de que deseja usar essa senha?" @@ -3651,7 +3648,7 @@ "message": "Importante:" }, "masterPasswordHint": { - "message": "Sua senha mestra não pode ser recuperada se você a esquecer!" + "message": "Sua senha principal não pode ser recuperada se você a esquecer!" }, "characterMinimum": { "message": "Mínimo de $LENGTH$ caracteres", @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Não é possível preencher automaticamente" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "A correspondência padrão está configurada como 'Correspondência exata'. O site atual não corresponde exatamente aos detalhes salvos de credencial para este item." }, "okay": { - "message": "Okay" + "message": "Ok" }, "toggleSideNavigation": { "message": "Habilitar navegação lateral" @@ -4208,7 +4205,7 @@ "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha mestra" + "message": "Usar a senha principal" }, "usePin": { "message": "Usar PIN" @@ -4422,7 +4419,7 @@ "message": "Código" }, "lastPassMasterPassword": { - "message": "Senha mestra do LastPass" + "message": "Senha principal do LastPass" }, "lastPassAuthRequired": { "message": "Autenticação do LastPass necessária" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Padrão ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar detecção de correspondência $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Ótimo trabalho protegendo suas credenciais em risco!" }, + "upgradeNow": { + "message": "Faça upgrade agora" + }, + "builtInAuthenticator": { + "message": "Autenticador integrado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de arquivos" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitoramento de brechas" + }, + "andMoreFeatures": { + "message": "E mais!" + }, + "planDescPremium": { + "message": "Segurança on-line completa" + }, + "upgradeToPremium": { + "message": "Faça upgrade para o Premium" + }, "settingDisabledByPolicy": { "message": "Essa configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index d3acb309860..15c993ab768 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pedir biometria ao iniciar" }, - "premiumRequired": { - "message": "É necessária uma subscrição Premium" - }, - "premiumRequiredDesc": { - "message": "É necessária uma subscrição Premium para utilizar esta funcionalidade." - }, "authenticationTimeout": { "message": "Tempo limite de autenticação" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Deve adicionar o URL do servidor de base ou pelo menos um ambiente personalizado." }, + "selfHostedEnvMustUseHttps": { + "message": "Os URLs devem usar HTTPS." + }, "customEnvironment": { "message": "Ambiente personalizado" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Predefinido ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Mostrar deteção de correspondência para $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Excelente trabalho ao proteger as suas credenciais em risco!" }, + "upgradeNow": { + "message": "Atualizar agora" + }, + "builtInAuthenticator": { + "message": "Autenticador incorporado" + }, + "secureFileStorage": { + "message": "Armazenamento seguro de ficheiros" + }, + "emergencyAccess": { + "message": "Acesso de emergência" + }, + "breachMonitoring": { + "message": "Monitorização de violações" + }, + "andMoreFeatures": { + "message": "E muito mais!" + }, + "planDescPremium": { + "message": "Segurança total online" + }, + "upgradeToPremium": { + "message": "Atualizar para o Premium" + }, "settingDisabledByPolicy": { "message": "Esta configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 0206f473448..4e1ac8ae832 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Solicitați date biometrice la pornire" }, - "premiumRequired": { - "message": "Premium necesar" - }, - "premiumRequiredDesc": { - "message": "Pentru a utiliza această funcție este necesar un abonament Premium." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Mediu personalizat" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index b69494d472e..d59fc34f736 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Запрашивать биометрию при запуске" }, - "premiumRequired": { - "message": "Требуется Премиум" - }, - "premiumRequiredDesc": { - "message": "Для использования этой функции необходим Премиум." - }, "authenticationTimeout": { "message": "Таймаут аутентификации" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Вы должны добавить либо базовый URL сервера, либо хотя бы одно пользовательское окружение." }, + "selfHostedEnvMustUseHttps": { + "message": "URL должны использовать HTTPS." + }, "customEnvironment": { "message": "Пользовательское окружение" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "По умолчанию ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показать обнаружение совпадений $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Отличная работа по защите ваших логинов, подверженных риску!" }, + "upgradeNow": { + "message": "Изменить сейчас" + }, + "builtInAuthenticator": { + "message": "Встроенный аутентификатор" + }, + "secureFileStorage": { + "message": "Защищенное хранилище файлов" + }, + "emergencyAccess": { + "message": "Экстренный доступ" + }, + "breachMonitoring": { + "message": "Мониторинг нарушений" + }, + "andMoreFeatures": { + "message": "И многое другое!" + }, + "planDescPremium": { + "message": "Полная онлайн-защищенность" + }, + "upgradeToPremium": { + "message": "Обновить до Премиум" + }, "settingDisabledByPolicy": { "message": "Этот параметр отключен политикой вашей организации.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 60ce2436254..8c5961153eb 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "වාරික අවශ්ය" - }, - "premiumRequiredDesc": { - "message": "මෙම අංගය භාවිතා කිරීම සඳහා වාරික සාමාජිකත්වයක් අවශ්ය වේ." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "අභිරුචි පරිසරය" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index 46ff6837c70..b459c86c236 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Pri spustení požiadať o biometriu" }, - "premiumRequired": { - "message": "Vyžaduje sa prémiový účet" - }, - "premiumRequiredDesc": { - "message": "Na použitie tejto funkcie je potrebné prémiové členstvo." - }, "authenticationTimeout": { "message": "Časový limit overenia" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Musíte pridať buď základnú adresu URL servera, alebo aspoň jedno vlastné prostredie." }, + "selfHostedEnvMustUseHttps": { + "message": "Adresy URL musia používať HTTPS." + }, "customEnvironment": { "message": "Vlastné prostredie" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Predvolené ($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Zobraziť zisťovanie zhody $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Skvelá práca pri zabezpečení vašich ohrozených prihlasovacích údajov!" }, + "upgradeNow": { + "message": "Upgradovať teraz" + }, + "builtInAuthenticator": { + "message": "Zabudovaný autentifikátor" + }, + "secureFileStorage": { + "message": "Bezpečné ukladanie súborov" + }, + "emergencyAccess": { + "message": "Núdzový prístup" + }, + "breachMonitoring": { + "message": "Sledovanie únikov" + }, + "andMoreFeatures": { + "message": "A ešte viac!" + }, + "planDescPremium": { + "message": "Úplné online zabezpečenie" + }, + "upgradeToPremium": { + "message": "Upgradovať na Prémium" + }, "settingDisabledByPolicy": { "message": "Politika organizácie vypla toto nastavenie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 53f7a9d8f03..0a6266636b3 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ob zagonu zahtevaj biometrično preverjanje" }, - "premiumRequired": { - "message": "Potrebno je premium članstvo" - }, - "premiumRequiredDesc": { - "message": "Premium članstvo je potrebno za uporabo te funkcije." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Okolje po meri" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 169713c5047..0158ca6ba2b 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Захтевај биометрију при покретању" }, - "premiumRequired": { - "message": "Потребан Премијум" - }, - "premiumRequiredDesc": { - "message": "Премијум чланство је неопходно за употребу ове опције." - }, "authenticationTimeout": { "message": "Истекло је време аутентификације" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Прилагођено окружење" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Прикажи откривање подударања $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 3b84369db47..057a7ca746c 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -32,7 +32,7 @@ "message": "Använd Single Sign-On" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Din organisation kräver single sign-on." }, "welcomeBack": { "message": "Välkommen tillbaka" @@ -87,7 +87,7 @@ "message": "Huvudlösenordsledtråd (valfri)" }, "passwordStrengthScore": { - "message": "Lösenordsstyrka $SCORE$ (score)", + "message": "Lösenordsstyrka $SCORE$", "placeholders": { "score": { "content": "$1", @@ -108,7 +108,7 @@ } }, "finishJoiningThisOrganizationBySettingAMasterPassword": { - "message": "Avsluta anslutningen till denna organisation genom att ange ett huvudlösenord." + "message": "Slutför anslutningen till den här organisationen genom att ställa in ett huvudlösenord." }, "tab": { "message": "Flik" @@ -592,7 +592,7 @@ "message": "Visa" }, "viewAll": { - "message": "View all" + "message": "Visa alla" }, "viewLogin": { "message": "Visa inloggning" @@ -1035,10 +1035,10 @@ "message": "Objekt sparat" }, "savedWebsite": { - "message": "Saved website" + "message": "Sparad webbplats" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Sparade webbplatser ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Be om biometri vid start" }, - "premiumRequired": { - "message": "Premium krävs" - }, - "premiumRequiredDesc": { - "message": "Ett premium-medlemskap krävs för att använda den här funktionen." - }, "authenticationTimeout": { "message": "Timeout för autentisering" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Du måste lägga till antingen serverns bas-URL eller minst en anpassad miljö." }, + "selfHostedEnvMustUseHttps": { + "message": "Webbadresser måste använda HTTPS." + }, "customEnvironment": { "message": "Anpassad miljö" }, @@ -1695,28 +1692,28 @@ "message": "Stäng av autofyll" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Bekräfta autofyll" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Denna webbplats matchar inte dina sparade inloggningsuppgifter. Innan du fyller i dina inloggningsuppgifter, se till att det är en betrodd webbplats." }, "showInlineMenuLabel": { "message": "Visa förslag för autofyll i formulärfält" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Hur skyddar Bitwarden dina data från nätfiske?" }, "currentWebsite": { - "message": "Current website" + "message": "Aktuell webbplats" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Autofyll och lägg till denna webbplats" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Autofyll utan att lägga till" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Autofyll inte" }, "showInlineMenuIdentitiesLabel": { "message": "Visa identiteter som förslag" @@ -3280,7 +3277,7 @@ "message": "Dekrypteringsfel" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Fel vid hämtning av autofylldata" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden kunde inte dekryptera valvföremålet/valvföremålen som listas nedan." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Kan inte autofylla" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Standardmatchning är satt till 'Exakt matchning'. Den aktuella webbplatsen matchar inte exakt de sparade inloggningsuppgifterna för detta objekt." }, "okay": { - "message": "Okay" + "message": "Okej" }, "toggleSideNavigation": { "message": "Växla sidonavigering" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Standard ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Visa matchningsdetektering $WEBSITE", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Bra jobbat med att säkra upp dina inloggninar i riskzonen!" }, + "upgradeNow": { + "message": "Uppgradera nu" + }, + "builtInAuthenticator": { + "message": "Inbyggd autenticator" + }, + "secureFileStorage": { + "message": "Säker fillagring" + }, + "emergencyAccess": { + "message": "Nödåtkomst" + }, + "breachMonitoring": { + "message": "Intrångsmonitorering" + }, + "andMoreFeatures": { + "message": "och mer!" + }, + "planDescPremium": { + "message": "Komplett säkerhet online" + }, + "upgradeToPremium": { + "message": "Uppgradera till Premium" + }, "settingDisabledByPolicy": { "message": "Denna inställning är inaktiverad enligt din organisations policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Postnummer" }, "cardNumberLabel": { - "message": "Card number" + "message": "Kortnummer" } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index a72a4910ef8..43944875889 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "தொடங்கும் போது பயோமெட்ரிக்ஸைக் கேட்கவும்" }, - "premiumRequired": { - "message": "பிரீமியம் தேவை" - }, - "premiumRequiredDesc": { - "message": "இந்த அம்சத்தைப் பயன்படுத்த ஒரு பிரீமியம் மெம்பர்ஷிப் தேவை." - }, "authenticationTimeout": { "message": "அங்கீகரிப்பு டைம் அவுட்" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "நீங்கள் பேஸ் சர்வர் URL-ஐ அல்லது குறைந்தது ஒரு தனிப்பயன் சூழலைச் சேர்க்க வேண்டும்." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "தனிப்பயன் சூழல்" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "பொருத்தமான கண்டறிதலைக் காட்டு $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index f160e9a8cfa..39e6c0be881 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index dd27da81316..e92192dafa0 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Ask for biometrics on launch" }, - "premiumRequired": { - "message": "Premium Required" - }, - "premiumRequiredDesc": { - "message": "A Premium membership is required to use this feature." - }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "You must add either the base Server URL or at least one custom environment." }, + "selfHostedEnvMustUseHttps": { + "message": "URLs must use HTTPS." + }, "customEnvironment": { "message": "Custom Environment" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Show match detection $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Upgrade now" + }, + "builtInAuthenticator": { + "message": "Built-in authenticator" + }, + "secureFileStorage": { + "message": "Secure file storage" + }, + "emergencyAccess": { + "message": "Emergency access" + }, + "breachMonitoring": { + "message": "Breach monitoring" + }, + "andMoreFeatures": { + "message": "And more!" + }, + "planDescPremium": { + "message": "Complete online security" + }, + "upgradeToPremium": { + "message": "Upgrade to Premium" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index d982d0f3a1a..543560810fe 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -32,7 +32,7 @@ "message": "Çoklu oturum açma kullan" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Kuruluşunuz çoklu oturum açma gerektiriyor." }, "welcomeBack": { "message": "Tekrar hoş geldiniz" @@ -1035,10 +1035,10 @@ "message": "Hesap kaydedildi" }, "savedWebsite": { - "message": "Saved website" + "message": "Kayıtlı web sitesi" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Kayıtlı web siteleri ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Açılışta biyometri doğrulaması iste" }, - "premiumRequired": { - "message": "Premium gerekli" - }, - "premiumRequiredDesc": { - "message": "Bu özelliği kullanmak için premium üyelik gereklidir." - }, "authenticationTimeout": { "message": "Kimlik doğrulama zaman aşımı" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Temel Sunucu URL’sini veya en az bir özel ortam eklemelisiniz." }, + "selfHostedEnvMustUseHttps": { + "message": "URL'ler HTTPS kullanmalıdır." + }, "customEnvironment": { "message": "Özel ortam" }, @@ -1695,28 +1692,28 @@ "message": "Otomatik doldurmayı kapat" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Otomatik doldurmayı onayla" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Bu site kayıtlı hesap bilgilerinizle eşleşmiyor. Hesap bilgilerinizi doldurmadan önce sitenin güvenilir olduğundan emin olun." }, "showInlineMenuLabel": { "message": "Form alanlarında otomatik doldurma önerilerini göster" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden verilerinizi kimlik avı saldırılarından nasıl koruyor?" }, "currentWebsite": { - "message": "Current website" + "message": "Geçerli web sitesi" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Otomatik doldur ve bu siteyi ekle" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Eklemeden otomatik doldur" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Otomatik doldurma" }, "showInlineMenuIdentitiesLabel": { "message": "Kimlikleri öneri olarak göster" @@ -3280,7 +3277,7 @@ "message": "Şifre çözme sorunu" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Otomatik doldurma verileri alınırken hata oluştu" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden aşağıdaki kasa öğelerini deşifre edemedi." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Otomatik doldurulamıyor" }, "cannotAutofillExactMatch": { "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." }, "okay": { - "message": "Okay" + "message": "Tamam" }, "toggleSideNavigation": { "message": "Kenar menüsünü aç/kapat" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Varsayılan ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "$WEBSITE$ eşleşme tespitini göster", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "Great job securing your at-risk logins!" }, + "upgradeNow": { + "message": "Şimdi yükselt" + }, + "builtInAuthenticator": { + "message": "Dahili kimlik doğrulayıcı" + }, + "secureFileStorage": { + "message": "Güvenli dosya depolama" + }, + "emergencyAccess": { + "message": "Acil durum erişimi" + }, + "breachMonitoring": { + "message": "İhlal izleme" + }, + "andMoreFeatures": { + "message": "Ve daha fazlası!" + }, + "planDescPremium": { + "message": "Eksiksiz çevrimiçi güvenlik" + }, + "upgradeToPremium": { + "message": "Premium'a yükselt" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index aa118c0b93e..2c6fa4eb15b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -32,7 +32,7 @@ "message": "Використати єдиний вхід" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша організація вимагає єдиний вхід (SSO)." }, "welcomeBack": { "message": "З поверненням" @@ -592,7 +592,7 @@ "message": "Переглянути" }, "viewAll": { - "message": "View all" + "message": "Переглянути все" }, "viewLogin": { "message": "Переглянути запис" @@ -1035,10 +1035,10 @@ "message": "Запис збережено" }, "savedWebsite": { - "message": "Saved website" + "message": "Збережений вебсайт" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Збережені вебсайти ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1440,22 +1440,22 @@ "message": "Застаріле шифрування більше не підтримується. Зверніться до служби підтримки, щоб відновити обліковий запис." }, "premiumMembership": { - "message": "Преміум статус" + "message": "Передплата Premium" }, "premiumManage": { "message": "Керувати передплатою" }, "premiumManageAlert": { - "message": "Ви можете керувати своїм статусом у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" + "message": "Ви можете керувати передплатою у сховищі на bitwarden.com. Хочете перейти на вебсайт зараз?" }, "premiumRefresh": { "message": "Оновити стан передплати" }, "premiumNotCurrentMember": { - "message": "Зараз у вас немає передплати преміум." + "message": "Зараз у вас немає передплати Premium." }, "premiumSignUpAndGet": { - "message": "Передплатіть преміум і отримайте:" + "message": "Передплатіть Premium і отримайте:" }, "ppremiumSignUpStorage": { "message": "1 ГБ зашифрованого сховища для файлів." @@ -1476,25 +1476,25 @@ "message": "Пріоритетну технічну підтримку." }, "ppremiumSignUpFuture": { - "message": "Усі майбутні преміумфункції. Їх буде більше!" + "message": "Усі майбутні функції Premium. Їх буде більше!" }, "premiumPurchase": { - "message": "Придбати преміум" + "message": "Придбати Premium" }, "premiumPurchaseAlertV2": { - "message": "Ви можете придбати Преміум у налаштуваннях облікового запису вебпрограмі Bitwarden." + "message": "Ви можете придбати Premium у налаштуваннях облікового запису вебпрограми Bitwarden." }, "premiumCurrentMember": { - "message": "Ви користуєтеся передплатою преміум!" + "message": "Ви користуєтеся передплатою Premium!" }, "premiumCurrentMemberThanks": { "message": "Дякуємо за підтримку Bitwarden." }, "premiumFeatures": { - "message": "Передплатіть преміум та отримайте:" + "message": "Передплатіть Premium та отримайте:" }, "premiumPrice": { - "message": "Всього лише $PRICE$ / за рік!", + "message": "Лише $PRICE$ / рік!", "placeholders": { "price": { "content": "$1", @@ -1503,7 +1503,7 @@ } }, "premiumPriceV2": { - "message": "Усе лише за $PRICE$ на рік!", + "message": "Лише за $PRICE$ на рік за все!", "placeholders": { "price": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Запитувати біометрію під час запуску" }, - "premiumRequired": { - "message": "Необхідна передплата преміум" - }, - "premiumRequiredDesc": { - "message": "Для використання цієї функції необхідна передплата преміум." - }, "authenticationTimeout": { "message": "Час очікування автентифікації" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Необхідно додати URL-адресу основного сервера, або принаймні одне користувацьке середовище." }, + "selfHostedEnvMustUseHttps": { + "message": "URL-адреси повинні бути HTTPS." + }, "customEnvironment": { "message": "Власне середовище" }, @@ -1695,28 +1692,28 @@ "message": "Вимкніть автозаповнення" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Підтвердити автозаповнення" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Адреса цього вебсайту відрізняється від збережених даних вашого запису. Перш ніж заповнити облікові дані, переконайтеся, що це надійний сайт." }, "showInlineMenuLabel": { "message": "Пропозиції автозаповнення на полях форм" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Як Bitwarden захищає ваші дані від шахрайства?" }, "currentWebsite": { - "message": "Current website" + "message": "Поточний вебсайт" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Автоматично заповнити й додати цей сайт" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Автоматично заповнити без додавання" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Не заповнювати автоматично" }, "showInlineMenuIdentitiesLabel": { "message": "Показувати посвідчення як пропозиції" @@ -3280,7 +3277,7 @@ "message": "Помилка розшифрування" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Помилка отримання даних автозаповнення" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden не зміг розшифрувати вказані нижче елементи сховища." @@ -3512,7 +3509,7 @@ "message": "Помилка Key Connector: переконайтеся, що Key Connector доступний та працює правильно." }, "premiumSubcriptionRequired": { - "message": "Необхідна передплата преміум" + "message": "Необхідна передплата Premium" }, "organizationIsDisabled": { "message": "Організацію вимкнено." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Неможливо автоматично заповнити" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Типово налаштовано \"Точну відповідність\". Адреса поточного вебсайту відрізняється від збережених даних для цього запису." }, "okay": { - "message": "Okay" + "message": "Гаразд" }, "toggleSideNavigation": { "message": "Перемкнути бічну навігацію" @@ -4891,7 +4888,7 @@ "message": "Ви дійсно хочете остаточно видалити це вкладення?" }, "premium": { - "message": "Преміум" + "message": "Premium" }, "freeOrgsCannotUseAttachments": { "message": "Організації без передплати не можуть використовувати вкладення" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Показати виявлення збігів $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Ви чудово впоралися із захистом своїх ризикованих записів!" }, + "upgradeNow": { + "message": "Покращити" + }, + "builtInAuthenticator": { + "message": "Вбудований автентифікатор" + }, + "secureFileStorage": { + "message": "Захищене сховище файлів" + }, + "emergencyAccess": { + "message": "Екстрений доступ" + }, + "breachMonitoring": { + "message": "Моніторинг витоків даних" + }, + "andMoreFeatures": { + "message": "Інші можливості!" + }, + "planDescPremium": { + "message": "Повна онлайн-безпека" + }, + "upgradeToPremium": { + "message": "Покращити до Premium" + }, "settingDisabledByPolicy": { "message": "Цей параметр вимкнено політикою вашої організації.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Поштовий індекс" }, "cardNumberLabel": { - "message": "Card number" + "message": "Номер картки" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index fff32a542cc..8029f5b2c46 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -32,7 +32,7 @@ "message": "Dùng đăng nhập một lần" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Tổ chức của bạn yêu cầu đăng nhập một lần." }, "welcomeBack": { "message": "Chào mừng bạn trở lại" @@ -592,7 +592,7 @@ "message": "Xem" }, "viewAll": { - "message": "View all" + "message": "Xem tất cả" }, "viewLogin": { "message": "Xem đăng nhập" @@ -1035,10 +1035,10 @@ "message": "Đã lưu mục" }, "savedWebsite": { - "message": "Saved website" + "message": "Đã lưu trang web" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Đã lưu trang web ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "Yêu cầu sinh trắc học khi khởi chạy" }, - "premiumRequired": { - "message": "Cần có tài khoản Cao cấp" - }, - "premiumRequiredDesc": { - "message": "Cần là thành viên Cao cấp để sử dụng tính năng này." - }, "authenticationTimeout": { "message": "Thời gian chờ xác thực" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "Bạn phải thêm URL máy chủ cơ sở hoặc ít nhất một môi trường tùy chỉnh." }, + "selfHostedEnvMustUseHttps": { + "message": "URL phải sử dụng HTTPS." + }, "customEnvironment": { "message": "Môi trường tùy chỉnh" }, @@ -1695,28 +1692,28 @@ "message": "Tắt tự động điền" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Xác nhận tự động điền" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Trang web này không khớp với đăng nhập đã lưu của bạn. Trước khi bạn điền thông tin đăng nhập, hãy đảm bảo đây là trang web đáng tin cậy." }, "showInlineMenuLabel": { "message": "Hiển thị các gợi ý tự động điền trên các trường biểu mẫu" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden bảo vệ dữ liệu của bạn khỏi lừa đảo như thế nào?" }, "currentWebsite": { - "message": "Current website" + "message": "Trang web hiện tại" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Tự động điền và thêm trang web này" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Tự động điền mà không thêm" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Không tự động điền" }, "showInlineMenuIdentitiesLabel": { "message": "Hiển thị danh tính dưới dạng gợi ý" @@ -3280,7 +3277,7 @@ "message": "Lỗi giải mã" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Lỗi khi lấy dữ liệu tự động điền" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden không thể giải mã các mục trong kho lưu trữ được liệt kê bên dưới." @@ -4054,13 +4051,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Không thể tự động điền" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Phép so khớp mặc định được đặt thành \"Khớp chính xác\". Trang web hiện tại không khớp chính xác với đăng nhập đã lưu cho mục này." }, "okay": { - "message": "Okay" + "message": "Đồng ý" }, "toggleSideNavigation": { "message": "Ẩn/hiện thanh điều hướng bên" @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "Hiện phát hiện trùng khớp $WEBSITE$", "placeholders": { @@ -5769,14 +5776,38 @@ "atRiskLoginsSecured": { "message": "Thật tuyệt khi bảo vệ các đăng nhập có nguy cơ của bạn!" }, + "upgradeNow": { + "message": "Nâng cấp ngay" + }, + "builtInAuthenticator": { + "message": "Trình xác thực tích hợp" + }, + "secureFileStorage": { + "message": "Lưu trữ tệp an toàn" + }, + "emergencyAccess": { + "message": "Truy cập khẩn cấp" + }, + "breachMonitoring": { + "message": "Giám sát vi phạm" + }, + "andMoreFeatures": { + "message": "Và nhiều hơn nữa!" + }, + "planDescPremium": { + "message": "Bảo mật trực tuyến toàn diện" + }, + "upgradeToPremium": { + "message": "Nâng cấp lên gói Cao cấp" + }, "settingDisabledByPolicy": { "message": "Cài đặt này bị vô hiệu hóa bởi chính sách tổ chức của bạn.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Mã ZIP / Bưu điện" }, "cardNumberLabel": { - "message": "Card number" + "message": "Số thẻ" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 16f41e4e987..e59a74e358d 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -32,7 +32,7 @@ "message": "使用单点登录" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的组织要求单点登录。" }, "welcomeBack": { "message": "欢迎回来" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "启动时提示生物识别" }, - "premiumRequired": { - "message": "需要高级会员" - }, - "premiumRequiredDesc": { - "message": "使用此功能需要高级会员资格。" - }, "authenticationTimeout": { "message": "身份验证超时" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "您必须添加基础服务器 URL 或至少添加一个自定义环境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必须使用 HTTPS。" + }, "customEnvironment": { "message": "自定义环境" }, @@ -4968,7 +4965,17 @@ "message": "删除网站" }, "defaultLabel": { - "message": "默认 ($VALUE$)", + "message": "默认($VALUE$)", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, + "defaultLabelWithValue": { + "message": "默认($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5592,7 +5599,7 @@ "message": "欢迎使用 Bitwarden" }, "securityPrioritized": { - "message": "安全优先" + "message": "以安全为首要" }, "securityPrioritizedBody": { "message": "将登录、支付卡和身份保存到您的安全密码库。Bitwarden 使用零知识、端到端的加密来保护您的重要信息。" @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "很好地保护了存在风险的登录!" }, + "upgradeNow": { + "message": "立即升级" + }, + "builtInAuthenticator": { + "message": "内置身份验证器" + }, + "secureFileStorage": { + "message": "安全文件存储" + }, + "emergencyAccess": { + "message": "紧急访问" + }, + "breachMonitoring": { + "message": "数据泄露监测" + }, + "andMoreFeatures": { + "message": "以及更多!" + }, + "planDescPremium": { + "message": "全面的在线安全防护" + }, + "upgradeToPremium": { + "message": "升级为高级版" + }, "settingDisabledByPolicy": { "message": "此设置被您组织的策略禁用了。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index d3c0319e488..63f3ea59f60 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -32,7 +32,7 @@ "message": "使用單一登入" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "您的組織需要單一登入。" }, "welcomeBack": { "message": "歡迎回來" @@ -1523,12 +1523,6 @@ "enableAutoBiometricsPrompt": { "message": "啟動時要求生物特徵辨識" }, - "premiumRequired": { - "message": "需要進階會員資格" - }, - "premiumRequiredDesc": { - "message": "進階會員才可使用此功能。" - }, "authenticationTimeout": { "message": "驗證逾時" }, @@ -1641,6 +1635,9 @@ "selfHostedEnvFormInvalid": { "message": "您必須新增伺服器網域 URL 或至少一個自訂環境。" }, + "selfHostedEnvMustUseHttps": { + "message": "URL 必須使用 HTTPS。" + }, "customEnvironment": { "message": "自訂環境" }, @@ -4977,6 +4974,16 @@ } } }, + "defaultLabelWithValue": { + "message": "Default ( $VALUE$ )", + "description": "A label that indicates the default value for a field with the current default value in parentheses.", + "placeholders": { + "value": { + "content": "$1", + "example": "Base domain" + } + } + }, "showMatchDetection": { "message": "顯示偵測到的吻合 $WEBSITE$", "placeholders": { @@ -5769,6 +5776,30 @@ "atRiskLoginsSecured": { "message": "你已成功保護有風險的登入項目,做得好!" }, + "upgradeNow": { + "message": "立即升級" + }, + "builtInAuthenticator": { + "message": "內建驗證器" + }, + "secureFileStorage": { + "message": "安全檔案儲存" + }, + "emergencyAccess": { + "message": "緊急存取" + }, + "breachMonitoring": { + "message": "外洩監控" + }, + "andMoreFeatures": { + "message": "以及其他功能功能!" + }, + "planDescPremium": { + "message": "完整的線上安全" + }, + "upgradeToPremium": { + "message": "升級到 Premium" + }, "settingDisabledByPolicy": { "message": "此設定已被你的組織原則停用。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." From ed53ef19d9410932a6c884064484281e9b881c76 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 10 Nov 2025 14:15:15 +0100 Subject: [PATCH 067/249] [PM-27897] Fix release before use in chromium importer (#17276) We ran into some weird issues where the memory was corrupted on certain architectures. It turns out we free'd memory before using it. This ensures we make a copy of the data before freeing it, and extracts a common function for both crates to use. --- .../src/windows/crypto.rs | 44 ++------------- .../chromium_importer/src/chromium/mod.rs | 2 +- .../src/chromium/platform/windows/crypto.rs | 54 ++++++++++++++++++ .../src/chromium/platform/windows/mod.rs | 55 +------------------ 4 files changed, 63 insertions(+), 92 deletions(-) create mode 100644 apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs index 9b91746dd1d..094dbf94a67 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs @@ -2,17 +2,14 @@ use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit}; use anyhow::{anyhow, Result}; use base64::{engine::general_purpose, Engine as _}; use chacha20poly1305::ChaCha20Poly1305; +use chromium_importer::chromium::crypt_unprotect_data; use scopeguard::defer; use tracing::debug; use windows::{ core::w, - Win32::{ - Foundation::{LocalFree, HLOCAL}, - Security::Cryptography::{ - self, CryptUnprotectData, NCryptOpenKey, NCryptOpenStorageProvider, CERT_KEY_SPEC, - CRYPTPROTECT_UI_FORBIDDEN, CRYPT_INTEGER_BLOB, NCRYPT_FLAGS, NCRYPT_KEY_HANDLE, - NCRYPT_PROV_HANDLE, NCRYPT_SILENT_FLAG, - }, + Win32::Security::Cryptography::{ + self, NCryptOpenKey, NCryptOpenStorageProvider, CERT_KEY_SPEC, CRYPTPROTECT_UI_FORBIDDEN, + NCRYPT_FLAGS, NCRYPT_KEY_HANDLE, NCRYPT_PROV_HANDLE, NCRYPT_SILENT_FLAG, }, }; @@ -71,38 +68,7 @@ fn decrypt_with_dpapi(data: &[u8], expect_appb: bool) -> Result> { let data = if expect_appb { &data[4..] } else { data }; - let in_blob = CRYPT_INTEGER_BLOB { - cbData: data.len() as u32, - pbData: data.as_ptr() as *mut u8, - }; - - let mut out_blob = CRYPT_INTEGER_BLOB::default(); - - let result = unsafe { - CryptUnprotectData( - &in_blob, - None, - None, - None, - None, - CRYPTPROTECT_UI_FORBIDDEN, - &mut out_blob, - ) - }; - - if result.is_ok() && !out_blob.pbData.is_null() && out_blob.cbData > 0 { - let decrypted = unsafe { - std::slice::from_raw_parts(out_blob.pbData, out_blob.cbData as usize).to_vec() - }; - - // Free the memory allocated by CryptUnprotectData - unsafe { LocalFree(Some(HLOCAL(out_blob.pbData as *mut _))) }; - - Ok(decrypted) - } else { - debug!("CryptUnprotectData failed"); - Err(anyhow!("CryptUnprotectData failed")) - } + crypt_unprotect_data(data, CRYPTPROTECT_UI_FORBIDDEN) } // diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index c6bbd3af445..aec8a84b5c1 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -10,7 +10,7 @@ use rusqlite::{params, Connection}; mod platform; #[cfg(target_os = "windows")] -pub use platform::{verify_signature, ADMIN_TO_USER_PIPE_NAME}; +pub use platform::*; pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs new file mode 100644 index 00000000000..60f7b806033 --- /dev/null +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/crypto.rs @@ -0,0 +1,54 @@ +use anyhow::{anyhow, Result}; +use windows::Win32::{ + Foundation::{LocalFree, HLOCAL}, + Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB}, +}; + +/// Rust friendly wrapper around CryptUnprotectData +/// +/// Decrypts the data passed in using the `CryptUnprotectData` api. +pub fn crypt_unprotect_data(data: &[u8], flags: u32) -> Result> { + if data.is_empty() { + return Ok(Vec::new()); + } + + let data_in = CRYPT_INTEGER_BLOB { + cbData: data.len() as u32, + pbData: data.as_ptr() as *mut u8, + }; + + let mut data_out = CRYPT_INTEGER_BLOB::default(); + + let result = unsafe { + CryptUnprotectData( + &data_in, + None, // ppszDataDescr: Option<*mut PWSTR> + None, // pOptionalEntropy: Option<*const CRYPT_INTEGER_BLOB> + None, // pvReserved: Option<*const std::ffi::c_void> + None, // pPromptStruct: Option<*const CRYPTPROTECT_PROMPTSTRUCT> + flags, // dwFlags: u32 + &mut data_out, + ) + }; + + if result.is_err() { + return Err(anyhow!("CryptUnprotectData failed")); + } + + if data_out.pbData.is_null() || data_out.cbData == 0 { + return Ok(Vec::new()); + } + + let output_slice = + unsafe { std::slice::from_raw_parts(data_out.pbData, data_out.cbData as usize) }; + + // SAFETY: Must copy data before calling LocalFree() below. + // Calling to_vec() after LocalFree() causes use-after-free bugs. + let output = output_slice.to_vec(); + + unsafe { + LocalFree(Some(HLOCAL(data_out.pbData as *mut _))); + } + + Ok(output) +} diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs index a1191f2ebac..867104d9bfd 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs @@ -3,18 +3,16 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; use std::path::{Path, PathBuf}; -use windows::Win32::{ - Foundation::{LocalFree, HLOCAL}, - Security::Cryptography::{CryptUnprotectData, CRYPT_INTEGER_BLOB}, -}; use crate::chromium::{BrowserConfig, CryptoService, LocalState}; use crate::util; mod abe; mod abe_config; +mod crypto; mod signature; pub use abe_config::ADMIN_TO_USER_PIPE_NAME; +pub use crypto::*; pub use signature::*; // @@ -166,7 +164,7 @@ impl WindowsCryptoService { return Err(anyhow!("Encrypted master key is not encrypted with DPAPI")); } - let key = unprotect_data_win(&key_bytes[5..]) + let key = crypt_unprotect_data(&key_bytes[5..], 0) .map_err(|e| anyhow!("Failed to unprotect the master key: {}", e))?; Ok(key) @@ -209,53 +207,6 @@ impl WindowsCryptoService { } } -fn unprotect_data_win(data: &[u8]) -> Result> { - if data.is_empty() { - return Ok(Vec::new()); - } - - let data_in = CRYPT_INTEGER_BLOB { - cbData: data.len() as u32, - pbData: data.as_ptr() as *mut u8, - }; - - let mut data_out = CRYPT_INTEGER_BLOB { - cbData: 0, - pbData: std::ptr::null_mut(), - }; - - let result = unsafe { - CryptUnprotectData( - &data_in, - None, // ppszDataDescr: Option<*mut PWSTR> - None, // pOptionalEntropy: Option<*const CRYPT_INTEGER_BLOB> - None, // pvReserved: Option<*const std::ffi::c_void> - None, // pPromptStruct: Option<*const CRYPTPROTECT_PROMPTSTRUCT> - 0, // dwFlags: u32 - &mut data_out, - ) - }; - - if result.is_err() { - return Err(anyhow!("CryptUnprotectData failed")); - } - - if data_out.pbData.is_null() || data_out.cbData == 0 { - return Ok(Vec::new()); - } - - let output_slice = - unsafe { std::slice::from_raw_parts(data_out.pbData, data_out.cbData as usize) }; - - unsafe { - if !data_out.pbData.is_null() { - LocalFree(Some(HLOCAL(data_out.pbData as *mut std::ffi::c_void))); - } - } - - Ok(output_slice.to_vec()) -} - fn get_admin_exe_path() -> Result { let current_exe_full_path = std::env::current_exe() .map_err(|e| anyhow!("Failed to get current executable path: {}", e))?; From cb863b44d5b2d0df4b50d480675a71761b0d57cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:27:48 +0000 Subject: [PATCH 068/249] [PM-26430] Remove Type property from PolicyRequestModel to use route parameter only (#16960) * Remove Type property from PolicyRequestModel to use route parameter only * Remove PolicyType property from policy update request in auto-confirm edit policy dialog * Run prettier --- .../policies/auto-confirm-edit-policy-dialog.component.ts | 1 - .../organizations/policies/base-policy-edit.component.ts | 1 - .../vnext-organization-data-ownership.component.ts | 1 - libs/common/src/admin-console/models/request/policy.request.ts | 3 --- 4 files changed, 6 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts index bdc664e208e..99d484f04f2 100644 --- a/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/auto-confirm-edit-policy-dialog.component.ts @@ -234,7 +234,6 @@ export class AutoConfirmPolicyDialogComponent private async submitSingleOrg(): Promise { const singleOrgRequest: PolicyRequest = { - type: PolicyType.SingleOrg, enabled: true, data: null, }; diff --git a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts index 54d4491156c..c1b175fa988 100644 --- a/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/base-policy-edit.component.ts @@ -109,7 +109,6 @@ export abstract class BasePolicyEditComponent implements OnInit { } const request: PolicyRequest = { - type: this.policy.type, enabled: this.enabled.value ?? false, data: this.buildRequestData(), }; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts index 627f5762eda..a15c51ebf70 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts @@ -74,7 +74,6 @@ export class vNextOrganizationDataOwnershipPolicyComponent const request: VNextPolicyRequest = { policy: { - type: this.policy.type, enabled: this.enabled.value ?? false, data: this.buildRequestData(), }, diff --git a/libs/common/src/admin-console/models/request/policy.request.ts b/libs/common/src/admin-console/models/request/policy.request.ts index 7b2e4f76063..36ffadff128 100644 --- a/libs/common/src/admin-console/models/request/policy.request.ts +++ b/libs/common/src/admin-console/models/request/policy.request.ts @@ -1,7 +1,4 @@ -import { PolicyType } from "../../enums"; - export type PolicyRequest = { - type: PolicyType; enabled: boolean; data: any; }; From e7995256cd58acb88a990071eb2931ab617ae746 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:29:58 -0500 Subject: [PATCH 069/249] [deps] Platform: Update @types/node to v22.19.0 (#17297) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index b6e402a3ef6..a4286aabed9 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -19,7 +19,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.18.11", + "@types/node": "22.19.0", "typescript": "5.4.2" } }, @@ -117,9 +117,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", - "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", "license": "MIT", "peer": true, "dependencies": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 285997f6482..55699af47dd 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -24,7 +24,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.18.11", + "@types/node": "22.19.0", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/package-lock.json b/package-lock.json index e456e257ca4..07e98938cd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,7 +112,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.18.11", + "@types/node": "22.19.0", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.16", @@ -14391,9 +14391,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.18.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.11.tgz", - "integrity": "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ==", + "version": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index e224fd00213..21eb2b0c06d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.18.11", + "@types/node": "22.19.0", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.16", From 2f3f2c2105943cd9b752f3955750e4dc58ca1e21 Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:33:43 -0500 Subject: [PATCH 070/249] [PM-27737] text and triangle color (#17198) --- apps/web/src/locales/en/messages.json | 3 +++ .../access-intelligence/activity/all-activity.component.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5c712c98e0d..9ffa175af04 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index d0751556517..d8ad785ff14 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -46,7 +46,7 @@
  • Date: Mon, 10 Nov 2025 09:51:51 -0600 Subject: [PATCH 071/249] [PM-23713] always append query param to premium redirect (#17240) --- .../premium-upgrade-dialog.component.spec.ts | 35 +------------------ .../premium-upgrade-dialog.component.ts | 12 +++---- 2 files changed, 5 insertions(+), 42 deletions(-) diff --git a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.spec.ts b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.spec.ts index f2991cc41b4..107eb068e76 100644 --- a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.spec.ts +++ b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.spec.ts @@ -158,12 +158,7 @@ describe("PremiumUpgradeDialogComponent", () => { }); describe("upgrade()", () => { - it("should launch URI with query parameter for cloud-hosted environments", async () => { - mockEnvironmentService.environment$ = of({ - getWebVaultUrl: () => "https://vault.bitwarden.com", - getRegion: () => Region.US, - } as any); - + it("should launch URI with query parameter", async () => { await component["upgrade"](); expect(mockPlatformUtilsService.launchUri).toHaveBeenCalledWith( @@ -171,34 +166,6 @@ describe("PremiumUpgradeDialogComponent", () => { ); expect(mockDialogRef.close).toHaveBeenCalled(); }); - - it("should launch URI without query parameter for self-hosted environments", async () => { - mockEnvironmentService.environment$ = of({ - getWebVaultUrl: () => "https://self-hosted.example.com", - getRegion: () => Region.SelfHosted, - } as any); - - await component["upgrade"](); - - expect(mockPlatformUtilsService.launchUri).toHaveBeenCalledWith( - "https://self-hosted.example.com/#/settings/subscription/premium", - ); - expect(mockDialogRef.close).toHaveBeenCalled(); - }); - - it("should launch URI with query parameter for EU cloud region", async () => { - mockEnvironmentService.environment$ = of({ - getWebVaultUrl: () => "https://vault.bitwarden.eu", - getRegion: () => Region.EU, - } as any); - - await component["upgrade"](); - - expect(mockPlatformUtilsService.launchUri).toHaveBeenCalledWith( - "https://vault.bitwarden.eu/#/settings/subscription/premium?callToAction=upgradeToPremium", - ); - expect(mockDialogRef.close).toHaveBeenCalled(); - }); }); it("should close dialog when close button clicked", () => { diff --git a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts index d20c0d668c4..48286a5d18c 100644 --- a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts +++ b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts @@ -11,10 +11,7 @@ import { SubscriptionCadence, SubscriptionCadenceIds, } from "@bitwarden/common/billing/types/subscription-pricing-tier"; -import { - EnvironmentService, - Region, -} from "@bitwarden/common/platform/abstractions/environment.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { @@ -82,10 +79,9 @@ export class PremiumUpgradeDialogComponent { protected async upgrade(): Promise { const environment = await firstValueFrom(this.environmentService.environment$); - let vaultUrl = environment.getWebVaultUrl() + "/#/settings/subscription/premium"; - if (environment.getRegion() !== Region.SelfHosted) { - vaultUrl += "?callToAction=upgradeToPremium"; - } + const vaultUrl = + environment.getWebVaultUrl() + + "/#/settings/subscription/premium?callToAction=upgradeToPremium"; this.platformUtilsService.launchUri(vaultUrl); this.dialogRef.close(); } From 5aa6d38d802001f8a28d921798db16e42f788b14 Mon Sep 17 00:00:00 2001 From: Patrick-Pimentel-Bitwarden Date: Mon, 10 Nov 2025 10:54:25 -0500 Subject: [PATCH 072/249] feat(prelogin): [Auth/PM-23801] Move Prelogin Request (#17080) * feat(prelogin): [PM-23801] Move Prelogin Request - Initial implementation. * test(prelogin): [PM-23801] Move Prelogin Request - Removed unneeded test. --- .../src/angular/login/login.component.html | 2 +- .../src/angular/login/login.component.spec.ts | 102 ++++++ .../auth/src/angular/login/login.component.ts | 19 ++ .../abstractions/login-strategy.service.ts | 6 +- .../password-login.strategy.spec.ts | 2 +- .../password-login.strategy.ts | 5 +- .../login-strategy.service.spec.ts | 323 +++++++++++++++++- .../login-strategy.service.ts | 156 ++++++++- libs/common/src/enums/feature-flag.enum.ts | 2 + 9 files changed, 597 insertions(+), 20 deletions(-) create mode 100644 libs/auth/src/angular/login/login.component.spec.ts diff --git a/libs/auth/src/angular/login/login.component.html b/libs/auth/src/angular/login/login.component.html index 9faa582c071..e33872829ad 100644 --- a/libs/auth/src/angular/login/login.component.html +++ b/libs/auth/src/angular/login/login.component.html @@ -1,4 +1,4 @@ - (false); @@ -52,6 +52,22 @@ export class NavItemComponent extends NavBaseComponent { return this.forceActiveStyles() || (this._isActive && !this.hideActiveStyles()); } + /** + * adding calculation for tree variant due to needing visual alignment on different indentation levels needed between the first level and subsequent levels + */ + protected readonly navItemIndentationPadding = computed(() => { + const open = this.sideNavService.open; + const depth = this.treeDepth() ?? 0; + + if (open && this.variant() === "tree") { + return depth === 1 + ? `${this.TREE_BASE_PADDING}rem` + : `${this.TREE_BASE_PADDING + (depth - 1) * this.TREE_DEPTH_PADDING}rem`; + } + + return `${this.TREE_BASE_PADDING * depth}rem`; + }); + /** * Allow overriding of the RouterLink['ariaCurrentWhenActive'] property. * From c22cba76ecffbe8f9a1b222c0ac4540fdf5b5bae Mon Sep 17 00:00:00 2001 From: Vicki League Date: Mon, 10 Nov 2025 17:11:35 -0500 Subject: [PATCH 081/249] [PM-26984] Use medium instead of semibold or bold, and for headings (#17184) --- apps/desktop/src/scss/environment.scss | 2 +- apps/desktop/src/scss/modal.scss | 4 ++-- .../layouts/header/web-header.component.html | 2 +- .../access/access-list.component.html | 2 +- .../service-accounts-list.component.html | 2 +- .../shared/projects-list.component.html | 2 +- .../shared/secrets-list.component.html | 2 +- .../src/multi-select/scss/bw.theme.scss | 4 ++-- libs/components/src/tw-theme-preflight.css | 20 +++++++++++++------ .../src/typography/typography.directive.ts | 12 +++++------ 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/apps/desktop/src/scss/environment.scss b/apps/desktop/src/scss/environment.scss index e1356178208..699f2246b4a 100644 --- a/apps/desktop/src/scss/environment.scss +++ b/apps/desktop/src/scss/environment.scss @@ -21,7 +21,7 @@ padding-left: 15px; span { - font-weight: 600; + font-weight: 500; font-size: $font-size-small; } } diff --git a/apps/desktop/src/scss/modal.scss b/apps/desktop/src/scss/modal.scss index 1d86b1e880a..b3994946394 100644 --- a/apps/desktop/src/scss/modal.scss +++ b/apps/desktop/src/scss/modal.scss @@ -47,7 +47,7 @@ $modal-sm: 300px !default; $modal-transition: transform 0.3s ease-out !default; $close-font-size: $font-size-base * 1.5 !default; -$close-font-weight: bold !default; +$close-font-weight: 500 !default; $close-color: $black !default; $close-text-shadow: 0 1px 0 $white !default; @@ -218,7 +218,7 @@ $close-text-shadow: 0 1px 0 $white !default; h5 { font-size: $font-size-base; - font-weight: bold; + font-weight: 500; display: flex; align-items: center; diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index 992ba147075..4b833e771dd 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -12,7 +12,7 @@

    diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html index 3399b550ba5..6172ec22b65 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.html @@ -21,7 +21,7 @@

- - - - - + {{ "pendingCancellation" | i18n }} + + +
+
{{ "nextChargeHeader" | i18n }}
+
+ + +
+ + {{ + (sub.subscription.periodEndDate | date: "MMM d, y") + + ", " + + (discountedSubscriptionAmount | currency: "$") + }} + + +
+
+ +
+ + {{ + (sub.subscription.periodEndDate | date: "MMM d, y") + + ", " + + (subscriptionAmount | currency: "$") + }} + +
+
+
+ - +
+
@@ -90,8 +112,27 @@
- -
+
+

{{ "storage" | i18n }}

+

+ {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }} +

+ + +
+
+ + +
+
+
+

{{ "additionalOptions" | i18n }}

+

{{ "additionalOptionsDesc" | i18n }}

+
-

{{ "storage" | i18n }}

-

- {{ "subscriptionStorage" | i18n: sub.maxStorageGb || 0 : sub.storageName || "0 MB" }} -

- - -
-
- - -
-
-
- +
diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 19db9ec8e61..c39b5d153b1 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -7,13 +7,17 @@ import { firstValueFrom, lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { DiscountInfo } from "@bitwarden/pricing"; import { AdjustStorageDialogComponent, @@ -42,6 +46,10 @@ export class UserSubscriptionComponent implements OnInit { cancelPromise: Promise; reinstatePromise: Promise; + protected enableDiscountDisplay$ = this.configService.getFeatureFlag$( + FeatureFlag.PM23341_Milestone_2, + ); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -54,6 +62,7 @@ export class UserSubscriptionComponent implements OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, private accountService: AccountService, + private configService: ConfigService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -187,6 +196,28 @@ export class UserSubscriptionComponent implements OnInit { return this.sub != null ? this.sub.upcomingInvoice : null; } + get subscriptionAmount(): number { + if (!this.subscription?.items || this.subscription.items.length === 0) { + return 0; + } + + return this.subscription.items.reduce( + (sum, item) => sum + (item.amount || 0) * (item.quantity || 0), + 0, + ); + } + + get discountedSubscriptionAmount(): number { + // Use the upcoming invoice amount from the server as it already includes discounts, + // taxes, prorations, and all other adjustments. Fall back to subscription amount + // if upcoming invoice is not available. + if (this.nextInvoice?.amount != null) { + return this.nextInvoice.amount; + } + + return this.subscriptionAmount; + } + get storagePercentage() { return this.sub != null && this.sub.maxStorageGb ? +(100 * (this.sub.storageGb / this.sub.maxStorageGb)).toFixed(2) @@ -217,4 +248,15 @@ export class UserSubscriptionComponent implements OnInit { return this.subscription.status; } } + + getDiscountInfo(discount: BillingCustomerDiscount | null): DiscountInfo | null { + if (!discount) { + return null; + } + return { + active: discount.active, + percentOff: discount.percentOff, + amountOff: discount.amountOff, + }; + } } diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index fb593b39328..12792cd781a 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -1,6 +1,7 @@ import { NgModule } from "@angular/core"; import { BannerModule } from "@bitwarden/components"; +import { DiscountBadgeComponent } from "@bitwarden/pricing"; import { EnterBillingAddressComponent, EnterPaymentMethodComponent, @@ -28,6 +29,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; BannerModule, EnterPaymentMethodComponent, EnterBillingAddressComponent, + DiscountBadgeComponent, ], declarations: [ BillingHistoryComponent, @@ -51,6 +53,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; OffboardingSurveyComponent, IndividualSelfHostingLicenseUploaderComponent, OrganizationSelfHostingLicenseUploaderComponent, + DiscountBadgeComponent, ], }) export class BillingSharedModule {} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 49e29f00748..27faf6f4063 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -3250,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, diff --git a/libs/common/src/billing/models/response/organization-subscription.response.ts b/libs/common/src/billing/models/response/organization-subscription.response.ts index 6e56eda68c6..f5fdaaba9b2 100644 --- a/libs/common/src/billing/models/response/organization-subscription.response.ts +++ b/libs/common/src/billing/models/response/organization-subscription.response.ts @@ -40,6 +40,7 @@ export class BillingCustomerDiscount extends BaseResponse { id: string; active: boolean; percentOff?: number; + amountOff?: number; appliesTo: string[]; constructor(response: any) { @@ -47,6 +48,7 @@ export class BillingCustomerDiscount extends BaseResponse { this.id = this.getResponseProperty("Id"); this.active = this.getResponseProperty("Active"); this.percentOff = this.getResponseProperty("PercentOff"); - this.appliesTo = this.getResponseProperty("AppliesTo"); + this.amountOff = this.getResponseProperty("AmountOff"); + this.appliesTo = this.getResponseProperty("AppliesTo") || []; } } diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts index 3bc7d42651c..01ace1ef10a 100644 --- a/libs/common/src/billing/models/response/subscription.response.ts +++ b/libs/common/src/billing/models/response/subscription.response.ts @@ -2,12 +2,15 @@ // @ts-strict-ignore import { BaseResponse } from "../../../models/response/base.response"; +import { BillingCustomerDiscount } from "./organization-subscription.response"; + export class SubscriptionResponse extends BaseResponse { storageName: string; storageGb: number; maxStorageGb: number; subscription: BillingSubscriptionResponse; upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse; + customerDiscount: BillingCustomerDiscount; license: any; expiration: string; @@ -20,11 +23,14 @@ export class SubscriptionResponse extends BaseResponse { this.expiration = this.getResponseProperty("Expiration"); const subscription = this.getResponseProperty("Subscription"); const upcomingInvoice = this.getResponseProperty("UpcomingInvoice"); + const customerDiscount = this.getResponseProperty("CustomerDiscount"); this.subscription = subscription == null ? null : new BillingSubscriptionResponse(subscription); this.upcomingInvoice = upcomingInvoice == null ? null : new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice); + this.customerDiscount = + customerDiscount == null ? null : new BillingCustomerDiscount(customerDiscount); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 2d071259aba..7d2d831bfb3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -33,6 +33,7 @@ export enum FeatureFlag { PM26793_FetchPremiumPriceFromPricingService = "pm-26793-fetch-premium-price-from-pricing-service", PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog", PM26462_Milestone_3 = "pm-26462-milestone-3", + PM23341_Milestone_2 = "pm-23341-milestone-2", /* Key Management */ PrivateKeyRegeneration = "pm-12241-private-key-regeneration", @@ -129,6 +130,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PM26793_FetchPremiumPriceFromPricingService]: FALSE, [FeatureFlag.PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog]: FALSE, [FeatureFlag.PM26462_Milestone_3]: FALSE, + [FeatureFlag.PM23341_Milestone_2]: FALSE, /* Key Management */ [FeatureFlag.PrivateKeyRegeneration]: FALSE, diff --git a/libs/pricing/src/components/discount-badge/discount-badge.component.html b/libs/pricing/src/components/discount-badge/discount-badge.component.html new file mode 100644 index 00000000000..e79fbabf355 --- /dev/null +++ b/libs/pricing/src/components/discount-badge/discount-badge.component.html @@ -0,0 +1,10 @@ + + {{ getDiscountText() }} + diff --git a/libs/pricing/src/components/discount-badge/discount-badge.component.mdx b/libs/pricing/src/components/discount-badge/discount-badge.component.mdx new file mode 100644 index 00000000000..d3df2dcf0f6 --- /dev/null +++ b/libs/pricing/src/components/discount-badge/discount-badge.component.mdx @@ -0,0 +1,67 @@ +import { Meta, Story, Canvas } from "@storybook/addon-docs"; +import * as DiscountBadgeStories from "./discount-badge.component.stories"; + + + +# Discount Badge + +A reusable UI component for displaying discount information (percentage or fixed amount) in a badge +format. + + + +## Usage + +The discount badge component is designed to be used in billing and subscription interfaces to +display discount information. + +```ts +import { DiscountBadgeComponent, DiscountInfo } from "@bitwarden/pricing"; +``` + +```html + +``` + +## API + +### Inputs + +| Input | Type | Description | +| ---------- | ---------------------- | -------------------------------------------------------------------------------- | +| `discount` | `DiscountInfo \| null` | **Optional.** Discount information object. If null or inactive, badge is hidden. | + +### DiscountInfo Interface + +```ts +interface DiscountInfo { + /** Whether the discount is currently active */ + active: boolean; + /** Percentage discount (0-100 or 0-1 scale) */ + percentOff?: number; + /** Fixed amount discount in the base currency */ + amountOff?: number; +} +``` + +## Behavior + +- The badge is only displayed when `discount` is provided, `active` is `true`, and either + `percentOff` or `amountOff` is greater than 0. +- If both `percentOff` and `amountOff` are provided, `percentOff` takes precedence. +- Percentage values can be provided as 0-100 (e.g., `20` for 20%) or 0-1 (e.g., `0.2` for 20%). +- Amount values are formatted as currency (USD) with 2 decimal places. + +## Examples + +### Percentage Discount + + + +### Amount Discount + + + +### Inactive Discount + + diff --git a/libs/pricing/src/components/discount-badge/discount-badge.component.spec.ts b/libs/pricing/src/components/discount-badge/discount-badge.component.spec.ts new file mode 100644 index 00000000000..8ccfc5e5d8b --- /dev/null +++ b/libs/pricing/src/components/discount-badge/discount-badge.component.spec.ts @@ -0,0 +1,108 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + +import { DiscountBadgeComponent } from "./discount-badge.component"; + +describe("DiscountBadgeComponent", () => { + let component: DiscountBadgeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [DiscountBadgeComponent], + providers: [ + { + provide: I18nService, + useValue: { + t: (key: string) => key, + }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(DiscountBadgeComponent); + component = fixture.componentInstance; + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); + + describe("hasDiscount", () => { + it("should return false when discount is null", () => { + fixture.componentRef.setInput("discount", null); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(false); + }); + + it("should return false when discount is inactive", () => { + fixture.componentRef.setInput("discount", { active: false, percentOff: 20 }); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(false); + }); + + it("should return true when discount is active with percentOff", () => { + fixture.componentRef.setInput("discount", { active: true, percentOff: 20 }); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(true); + }); + + it("should return true when discount is active with amountOff", () => { + fixture.componentRef.setInput("discount", { active: true, amountOff: 10.99 }); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(true); + }); + + it("should return false when percentOff is 0", () => { + fixture.componentRef.setInput("discount", { active: true, percentOff: 0 }); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(false); + }); + + it("should return false when amountOff is 0", () => { + fixture.componentRef.setInput("discount", { active: true, amountOff: 0 }); + fixture.detectChanges(); + expect(component.hasDiscount()).toBe(false); + }); + }); + + describe("getDiscountText", () => { + it("should return null when discount is null", () => { + fixture.componentRef.setInput("discount", null); + fixture.detectChanges(); + expect(component.getDiscountText()).toBeNull(); + }); + + it("should return percentage text when percentOff is provided", () => { + fixture.componentRef.setInput("discount", { active: true, percentOff: 20 }); + fixture.detectChanges(); + const text = component.getDiscountText(); + expect(text).toContain("20%"); + expect(text).toContain("discount"); + }); + + it("should convert decimal percentOff to percentage", () => { + fixture.componentRef.setInput("discount", { active: true, percentOff: 0.15 }); + fixture.detectChanges(); + const text = component.getDiscountText(); + expect(text).toContain("15%"); + }); + + it("should return amount text when amountOff is provided", () => { + fixture.componentRef.setInput("discount", { active: true, amountOff: 10.99 }); + fixture.detectChanges(); + const text = component.getDiscountText(); + expect(text).toContain("$10.99"); + expect(text).toContain("discount"); + }); + + it("should prefer percentOff over amountOff", () => { + fixture.componentRef.setInput("discount", { active: true, percentOff: 25, amountOff: 10.99 }); + fixture.detectChanges(); + const text = component.getDiscountText(); + expect(text).toContain("25%"); + expect(text).not.toContain("$10.99"); + }); + }); +}); diff --git a/libs/pricing/src/components/discount-badge/discount-badge.component.stories.ts b/libs/pricing/src/components/discount-badge/discount-badge.component.stories.ts new file mode 100644 index 00000000000..02631a6b940 --- /dev/null +++ b/libs/pricing/src/components/discount-badge/discount-badge.component.stories.ts @@ -0,0 +1,123 @@ +import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BadgeModule } from "@bitwarden/components"; + +import { DiscountBadgeComponent, DiscountInfo } from "./discount-badge.component"; + +export default { + title: "Billing/Discount Badge", + component: DiscountBadgeComponent, + description: "A badge component that displays discount information (percentage or fixed amount).", + decorators: [ + moduleMetadata({ + imports: [BadgeModule], + providers: [ + { + provide: I18nService, + useValue: { + t: (key: string) => { + switch (key) { + case "discount": + return "discount"; + default: + return key; + } + }, + }, + }, + ], + }), + ], +} as Meta; + +type Story = StoryObj; + +export const PercentDiscount: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: true, + percentOff: 20, + } as DiscountInfo, + }, +}; + +export const PercentDiscountDecimal: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: true, + percentOff: 0.15, // 15% in decimal format + } as DiscountInfo, + }, +}; + +export const AmountDiscount: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: true, + amountOff: 10.99, + } as DiscountInfo, + }, +}; + +export const LargeAmountDiscount: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: true, + amountOff: 99.99, + } as DiscountInfo, + }, +}; + +export const InactiveDiscount: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: false, + percentOff: 20, + } as DiscountInfo, + }, +}; + +export const NoDiscount: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: null, + }, +}; + +export const PercentAndAmountPreferPercent: Story = { + render: (args) => ({ + props: args, + template: ``, + }), + args: { + discount: { + active: true, + percentOff: 25, + amountOff: 10.99, + } as DiscountInfo, + }, +}; diff --git a/libs/pricing/src/components/discount-badge/discount-badge.component.ts b/libs/pricing/src/components/discount-badge/discount-badge.component.ts new file mode 100644 index 00000000000..6057a4573e9 --- /dev/null +++ b/libs/pricing/src/components/discount-badge/discount-badge.component.ts @@ -0,0 +1,70 @@ +import { CommonModule } from "@angular/common"; +import { ChangeDetectionStrategy, Component, inject, input } from "@angular/core"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BadgeModule } from "@bitwarden/components"; + +/** + * Interface for discount information that can be displayed in the discount badge. + * This is abstracted from the response class to avoid tight coupling. + */ +export interface DiscountInfo { + /** Whether the discount is currently active */ + active: boolean; + /** Percentage discount (0-100 or 0-1 scale) */ + percentOff?: number; + /** Fixed amount discount in the base currency */ + amountOff?: number; +} + +@Component({ + selector: "billing-discount-badge", + templateUrl: "./discount-badge.component.html", + standalone: true, + imports: [CommonModule, BadgeModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DiscountBadgeComponent { + readonly discount = input(null); + + private i18nService = inject(I18nService); + + getDiscountText(): string | null { + const discount = this.discount(); + if (!discount) { + return null; + } + + if (discount.percentOff != null && discount.percentOff > 0) { + const percentValue = + discount.percentOff < 1 ? discount.percentOff * 100 : discount.percentOff; + return `${Math.round(percentValue)}% ${this.i18nService.t("discount")}`; + } + + if (discount.amountOff != null && discount.amountOff > 0) { + const formattedAmount = new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(discount.amountOff); + return `${formattedAmount} ${this.i18nService.t("discount")}`; + } + + return null; + } + + hasDiscount(): boolean { + const discount = this.discount(); + if (!discount) { + return false; + } + if (!discount.active) { + return false; + } + return ( + (discount.percentOff != null && discount.percentOff > 0) || + (discount.amountOff != null && discount.amountOff > 0) + ); + } +} diff --git a/libs/pricing/src/index.ts b/libs/pricing/src/index.ts index d7c7772bfcb..3405044529e 100644 --- a/libs/pricing/src/index.ts +++ b/libs/pricing/src/index.ts @@ -1,3 +1,4 @@ // Components export * from "./components/pricing-card/pricing-card.component"; export * from "./components/cart-summary/cart-summary.component"; +export * from "./components/discount-badge/discount-badge.component"; From 828fdbd169334208ba3f01a4b5ee18c3d3331c40 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 12 Nov 2025 21:27:14 +0100 Subject: [PATCH 106/249] [CL-905] Migrate CL/Badge to OnPush (#16959) --- .../src/badge-list/badge-list.component.html | 8 +- .../src/badge-list/badge-list.component.ts | 52 +++++++--- libs/components/src/badge/badge.component.ts | 98 +++++++++++-------- 3 files changed, 97 insertions(+), 61 deletions(-) diff --git a/libs/components/src/badge-list/badge-list.component.html b/libs/components/src/badge-list/badge-list.component.html index 18365cba268..d976b2d2cc4 100644 --- a/libs/components/src/badge-list/badge-list.component.html +++ b/libs/components/src/badge-list/badge-list.component.html @@ -1,15 +1,15 @@
- @for (item of filteredItems; track item; let last = $last) { + @for (item of filteredItems(); track item; let last = $last) { {{ item }} - @if (!last || isFiltered) { + @if (!last || isFiltered()) { , } } - @if (isFiltered) { + @if (isFiltered()) { - {{ "plusNMore" | i18n: (items().length - filteredItems.length).toString() }} + {{ "plusNMore" | i18n: (items().length - filteredItems().length).toString() }} }
diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index e3d1403be43..a5b306c12fc 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -1,38 +1,60 @@ -import { Component, OnChanges, input } from "@angular/core"; +import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; import { BadgeModule, BadgeVariant } from "../badge"; function transformMaxItems(value: number | undefined) { - return value == undefined ? undefined : Math.max(1, value); + return value == null ? undefined : Math.max(1, value); } -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +/** + * Displays a collection of badges in a horizontal, wrapping layout. + * + * The component automatically handles overflow by showing a limited number of badges + * followed by a "+N more" badge when `maxItems` is specified and exceeded. + * + * Each badge inherits the `variant` and `truncate` settings, ensuring visual consistency + * across the list. Badges are separated by commas for screen readers to improve accessibility. + */ @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", imports: [BadgeModule, I18nPipe], + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class BadgeListComponent implements OnChanges { - protected filteredItems: string[] = []; - protected isFiltered = false; - +export class BadgeListComponent { + /** + * The visual variant to apply to all badges in the list. + */ readonly variant = input("primary"); + + /** + * Items to display as badges. + */ readonly items = input([]); + + /** + * Whether to truncate long badge text with ellipsis. + */ readonly truncate = input(true); + /** + * Maximum number of badges to display before showing a "+N more" badge. + */ readonly maxItems = input(undefined, { transform: transformMaxItems }); - ngOnChanges() { + protected readonly filteredItems = computed(() => { const maxItems = this.maxItems(); + const items = this.items(); - if (maxItems == undefined || this.items().length <= maxItems) { - this.filteredItems = this.items(); - } else { - this.filteredItems = this.items().slice(0, maxItems - 1); + if (maxItems == null || items.length <= maxItems) { + return items; } - this.isFiltered = this.items().length > this.filteredItems.length; - } + return items.slice(0, maxItems - 1); + }); + + protected readonly isFiltered = computed(() => { + return this.items().length > this.filteredItems().length; + }); } diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 8a953b30226..55d7b719ccd 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -1,5 +1,12 @@ import { CommonModule } from "@angular/common"; -import { Component, ElementRef, HostBinding, input } from "@angular/core"; +import { + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + inject, + input, +} from "@angular/core"; import { FocusableElement } from "../shared/focusable-element"; @@ -44,27 +51,56 @@ const hoverStyles: Record = { ], }; /** - * Badges are primarily used as labels, counters, and small buttons. - - * Typically Badges are only used with text set to `text-xs`. If additional sizes are needed, the component configurations may be reviewed and adjusted. - - * The Badge directive can be used on a `` (non clickable events), or an `` or `
-
-
- -
- {{ url }} -
-
-
+
+ @for (url of savedUrls(); track url) { +
+ +
+ {{ url }} +
+
+
+ }
}

{{ "currentWebsite" | i18n }}

-
- {{ currentUrl }} +
+ {{ currentUrl() }}
- @if (!viewOnly) { + @if (!viewOnly()) { } - @if (!(showAutofillConfirmation$ | async)) { + @if (!(autofillConfirmationFlagEnabled$ | async)) { } diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index 5927da6c3d2..7b71c2b470f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -2,6 +2,7 @@ import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Router } from "@angular/router"; +import { mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; @@ -66,11 +67,6 @@ describe("ItemMoreOptionsComponent", () => { resolvedDefaultUriMatchStrategy$: uriMatchStrategy$.asObservable(), }; - const hasSearchText$ = new BehaviorSubject(false); - const vaultPopupItemsService = { - hasSearchText$: hasSearchText$.asObservable(), - }; - const baseCipher = { id: "cipher-1", login: { @@ -120,7 +116,7 @@ describe("ItemMoreOptionsComponent", () => { }, { provide: VaultPopupItemsService, - useValue: vaultPopupItemsService, + useValue: mock({}), }, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -153,7 +149,7 @@ describe("ItemMoreOptionsComponent", () => { expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); }); - it("calls the autofill service to autofill without showing the confirmation dialog when the feature flag is disabled or search text is not present", async () => { + it("calls the autofill service to autofill without showing the confirmation dialog when the feature flag is disabled", async () => { autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com" }); await component.doAutofill(); @@ -182,7 +178,7 @@ describe("ItemMoreOptionsComponent", () => { }); it("does not show the exact match dialog when the default match strategy is Exact and autofill confirmation is not to be shown", async () => { - // autofill confirmation dialog is not shown when either the feature flag is disabled or search text is not present + // autofill confirmation dialog is not shown when either the feature flag is disabled uriMatchStrategy$.next(UriMatchStrategy.Exact); autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); await component.doAutofill(); @@ -192,9 +188,8 @@ describe("ItemMoreOptionsComponent", () => { describe("autofill confirmation dialog", () => { beforeEach(() => { - // autofill confirmation dialog is shown when feature flag is enabled and search text is present + // autofill confirmation dialog is shown when feature flag is enabled featureFlag$.next(true); - hasSearchText$.next(true); uriMatchStrategy$.next(UriMatchStrategy.Domain); passwordRepromptService.passwordRepromptCheck.mockResolvedValue(true); }); @@ -208,7 +203,7 @@ describe("ItemMoreOptionsComponent", () => { expect(passwordRepromptService.passwordRepromptCheck).toHaveBeenCalledWith(baseCipher); }); - it("opens the autofill confirmation dialog with filtered saved URLs when the feature flag is enabled and search text is present", async () => { + it("opens the autofill confirmation dialog with filtered saved URLs when the feature flag is enabled", async () => { autofillSvc.currentAutofillTab$.next({ url: "https://page.example.com/path" }); const openSpy = mockConfirmDialogResult(AutofillConfirmationDialogResult.Canceled); @@ -216,8 +211,8 @@ describe("ItemMoreOptionsComponent", () => { expect(openSpy).toHaveBeenCalledTimes(1); const args = openSpy.mock.calls[0][1]; - expect(args.data.currentUrl).toBe("https://page.example.com/path"); - expect(args.data.savedUrls).toEqual([ + expect(args.data?.currentUrl).toBe("https://page.example.com/path"); + expect(args.data?.savedUrls).toEqual([ "https://one.example.com", "https://two.example.com/a", ]); diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index 1316a0d32b8..b498e7cd9a5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -84,10 +84,9 @@ export class ItemMoreOptionsComponent { protected autofillAllowed$ = this.vaultPopupAutofillService.autofillAllowed$; - protected showAutofillConfirmation$ = combineLatest([ - this.configService.getFeatureFlag$(FeatureFlag.AutofillConfirmation), - this.vaultPopupItemsService.hasSearchText$, - ]).pipe(map(([isFeatureFlagEnabled, hasSearchText]) => isFeatureFlagEnabled && hasSearchText)); + protected autofillConfirmationFlagEnabled$ = this.configService + .getFeatureFlag$(FeatureFlag.AutofillConfirmation) + .pipe(map((isFeatureFlagEnabled) => isFeatureFlagEnabled)); protected uriMatchStrategy$ = this.domainSettingsService.resolvedDefaultUriMatchStrategy$; @@ -210,7 +209,7 @@ export class ItemMoreOptionsComponent { const cipherHasAllExactMatchLoginUris = uris.length > 0 && uris.every((u) => u.uri && u.match === UriMatchStrategy.Exact); - const showAutofillConfirmation = await firstValueFrom(this.showAutofillConfirmation$); + const showAutofillConfirmation = await firstValueFrom(this.autofillConfirmationFlagEnabled$); const uriMatchStrategy = await firstValueFrom(this.uriMatchStrategy$); if ( From 0af77ced458defef4e55db8d234131ba2f541868 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:23:33 -0600 Subject: [PATCH 119/249] [PM-28173] Only send 1 seat in Families tax calculation (#17368) * Fix family seat count in calculation * Fix test --- .../services/upgrade-payment.service.spec.ts | 2 +- .../services/upgrade-payment.service.ts | 60 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts index e20d20b0770..9d17d62e4dc 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.spec.ts @@ -436,7 +436,7 @@ describe("UpgradePaymentService", () => { tier: "families", passwordManager: { additionalStorage: 0, - seats: 6, + seats: 1, sponsored: false, }, }, diff --git a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts index 9bb963c210d..94f1c816168 100644 --- a/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts +++ b/apps/web/src/app/billing/individual/upgrade/upgrade-payment/services/upgrade-payment.service.ts @@ -98,41 +98,37 @@ export class UpgradePaymentService { planDetails: PlanDetails, billingAddress: BillingAddress, ): Promise { + const isFamiliesPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Families; + const isPremiumPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Premium; + + let taxClientCall: Promise | null = null; + + if (isFamiliesPlan) { + // Currently, only Families plan is supported for organization plans + const request: OrganizationSubscriptionPurchase = { + tier: "families", + cadence: "annually", + passwordManager: { seats: 1, additionalStorage: 0, sponsored: false }, + }; + + taxClientCall = this.taxClient.previewTaxForOrganizationSubscriptionPurchase( + request, + billingAddress, + ); + } + + if (isPremiumPlan) { + taxClientCall = this.taxClient.previewTaxForPremiumSubscriptionPurchase(0, billingAddress); + } + + if (taxClientCall === null) { + throw new Error("Tax client call is not defined"); + } + try { - const isOrganizationPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Families; - const isPremiumPlan = planDetails.tier === PersonalSubscriptionPricingTierIds.Premium; - - let taxClientCall: Promise | null = null; - - if (isOrganizationPlan) { - const seats = this.getPasswordManagerSeats(planDetails); - if (seats === 0) { - throw new Error("Seats must be greater than 0 for organization plan"); - } - // Currently, only Families plan is supported for organization plans - const request: OrganizationSubscriptionPurchase = { - tier: "families", - cadence: "annually", - passwordManager: { seats, additionalStorage: 0, sponsored: false }, - }; - - taxClientCall = this.taxClient.previewTaxForOrganizationSubscriptionPurchase( - request, - billingAddress, - ); - } - - if (isPremiumPlan) { - taxClientCall = this.taxClient.previewTaxForPremiumSubscriptionPurchase(0, billingAddress); - } - - if (taxClientCall === null) { - throw new Error("Tax client call is not defined"); - } - const preview = await taxClientCall; return preview.tax; - } catch (error: unknown) { + } catch (error) { this.logService.error("Tax calculation failed:", error); throw error; } From df59f7820a2d08eab524ad35707e51e1632f842e Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 13 Nov 2025 13:33:05 -0600 Subject: [PATCH 120/249] [PM-28036] Sidebar for Critical apps shows incorrect data - fixed (#17363) * PM-28036 added the download button to the code * PM-28036 fix failing tests * PM-28036 added additional unit tests * PM-28036 fixed failed type testing * PM-28036 removed unwanted await from method --- .../critical-applications.component.html | 4 +- ...risk-insights-drawer-dialog.component.html | 18 ++ ...k-insights-drawer-dialog.component.spec.ts | 184 ++++++++++++++++++ .../risk-insights-drawer-dialog.component.ts | 70 ++++++- 4 files changed, 273 insertions(+), 3 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html index 0e757582855..04c7bd23797 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html @@ -22,7 +22,7 @@ type="button" class="tw-flex-1" tabindex="0" - (click)="dataService.setDrawerForOrgAtRiskMembers('criticalAppsAtRiskMembers')" + (click)="dataService.setDrawerForCriticalAtRiskMembers('criticalAppsAtRiskMembers')" > @if (drawerDetails.atRiskMemberDetails?.length > 0) { +
@@ -77,6 +86,15 @@ }} @if (drawerDetails.atRiskAppDetails?.length > 0) { +
{{ "application" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts index 2b5910ed99e..9066462b2b1 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.spec.ts @@ -3,8 +3,10 @@ import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { mock } from "jest-mock-extended"; import { DrawerDetails, DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DIALOG_DATA } from "@bitwarden/components"; +import { LogService } from "@bitwarden/logging"; import { I18nPipe } from "@bitwarden/ui-common"; import { RiskInsightsDrawerDialogComponent } from "./risk-insights-drawer-dialog.component"; @@ -48,6 +50,8 @@ describe("RiskInsightsDrawerDialogComponent", () => { let component: RiskInsightsDrawerDialogComponent; let fixture: ComponentFixture; const mockI18nService = mock(); + const mockFileDownloadService = mock(); + const mocklogService = mock(); const drawerDetails: DrawerDetails = { open: true, invokerId: "test-invoker", @@ -56,6 +60,7 @@ describe("RiskInsightsDrawerDialogComponent", () => { appAtRiskMembers: null, atRiskAppDetails: null, }; + mockI18nService.t.mockImplementation((key: string) => key); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -64,6 +69,8 @@ describe("RiskInsightsDrawerDialogComponent", () => { { provide: DIALOG_DATA, useValue: drawerDetails }, { provide: I18nPipe, useValue: mock() }, { provide: I18nService, useValue: mockI18nService }, + { provide: FileDownloadService, useValue: mockFileDownloadService }, + { provide: LogService, useValue: mocklogService }, ], }).compileComponents(); @@ -93,4 +100,181 @@ describe("RiskInsightsDrawerDialogComponent", () => { expect(component.isActiveDrawerType(DrawerType.AppAtRiskMembers)).toBeFalsy(); }); }); + describe("downloadAtRiskMembers", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should download CSV when drawer is open with correct type and has data", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskMembers, + atRiskMemberDetails: [ + { email: "user@example.com", atRiskPasswordCount: 5 }, + { email: "admin@example.com", atRiskPasswordCount: 3 }, + ], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + mockI18nService.t.mockImplementation((key: string) => key); + + await component.downloadAtRiskMembers(); + + expect(mockFileDownloadService.download).toHaveBeenCalledWith({ + fileName: expect.stringContaining("at-risk-members"), + blobData: expect.any(String), + blobOptions: { type: "text/plain" }, + }); + }); + + it("should not download when drawer is closed", async () => { + component.drawerDetails = { + open: false, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskMembers, + atRiskMemberDetails: [{ email: "user@example.com", atRiskPasswordCount: 5 }], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + await component.downloadAtRiskMembers(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when activeDrawerType is incorrect", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskApps, + atRiskMemberDetails: [{ email: "user@example.com", atRiskPasswordCount: 5 }], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + await component.downloadAtRiskMembers(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when atRiskMemberDetails is null", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskMembers, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + await component.downloadAtRiskMembers(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when atRiskMemberDetails is empty array", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskMembers, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + await component.downloadAtRiskMembers(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + }); + + describe("downloadAtRiskApplications", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should download CSV when drawer is open with correct type and has data", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskApps, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: [ + { applicationName: "App1", atRiskPasswordCount: 10 }, + { applicationName: "App2", atRiskPasswordCount: 7 }, + ], + }; + + await component.downloadAtRiskApplications(); + + expect(mockFileDownloadService.download).toHaveBeenCalledWith({ + fileName: expect.stringContaining("at-risk-applications"), + blobData: expect.any(String), + blobOptions: { type: "text/plain" }, + }); + }); + + it("should not download when drawer is closed", async () => { + component.drawerDetails = { + open: false, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskApps, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: [{ applicationName: "App1", atRiskPasswordCount: 10 }], + }; + + await component.downloadAtRiskApplications(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when activeDrawerType is incorrect", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskMembers, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: [{ applicationName: "App1", atRiskPasswordCount: 10 }], + }; + + await component.downloadAtRiskApplications(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when atRiskAppDetails is null", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskApps, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: null, + }; + + await component.downloadAtRiskApplications(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + + it("should not download when atRiskAppDetails is empty array", async () => { + component.drawerDetails = { + open: true, + invokerId: "test-invoker", + activeDrawerType: DrawerType.OrgAtRiskApps, + atRiskMemberDetails: [], + appAtRiskMembers: null, + atRiskAppDetails: [], + }; + + await component.downloadAtRiskApplications(); + + expect(mockFileDownloadService.download).not.toHaveBeenCalled(); + }); + }); }); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts index 82cddda542c..30863f38e43 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.ts @@ -1,7 +1,12 @@ import { Component, ChangeDetectionStrategy, Inject } from "@angular/core"; import { DrawerDetails, DrawerType } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DIALOG_DATA } from "@bitwarden/components"; +import { LogService } from "@bitwarden/logging"; +import { ExportHelper } from "@bitwarden/vault-export-core"; +import { exportToCSV } from "@bitwarden/web-vault/app/dirt/reports/report-utils"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; @Component({ @@ -10,7 +15,12 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; changeDetection: ChangeDetectionStrategy.OnPush, }) export class RiskInsightsDrawerDialogComponent { - constructor(@Inject(DIALOG_DATA) public drawerDetails: DrawerDetails) {} + constructor( + @Inject(DIALOG_DATA) public drawerDetails: DrawerDetails, + private fileDownloadService: FileDownloadService, + private i18nService: I18nService, + private logService: LogService, + ) {} // Get a list of drawer types get drawerTypes(): typeof DrawerType { @@ -20,4 +30,62 @@ export class RiskInsightsDrawerDialogComponent { isActiveDrawerType(type: DrawerType): boolean { return this.drawerDetails.activeDrawerType === type; } + + /** + * downloads at risk members as CSV + */ + downloadAtRiskMembers() { + try { + // Validate drawer is open and showing the correct drawer type + if ( + !this.drawerDetails.open || + this.drawerDetails.activeDrawerType !== DrawerType.OrgAtRiskMembers || + !this.drawerDetails.atRiskMemberDetails || + this.drawerDetails.atRiskMemberDetails.length === 0 + ) { + return; + } + + this.fileDownloadService.download({ + fileName: ExportHelper.getFileName("at-risk-members"), + blobData: exportToCSV(this.drawerDetails.atRiskMemberDetails, { + email: this.i18nService.t("email"), + atRiskPasswordCount: this.i18nService.t("atRiskPasswords"), + }), + blobOptions: { type: "text/plain" }, + }); + } catch (error) { + // Log error for debugging + this.logService.error("Failed to download at-risk members", error); + } + } + + /** + * downloads at risk applications as CSV + */ + downloadAtRiskApplications() { + try { + // Validate drawer is open and showing the correct drawer type + if ( + !this.drawerDetails.open || + this.drawerDetails.activeDrawerType !== DrawerType.OrgAtRiskApps || + !this.drawerDetails.atRiskAppDetails || + this.drawerDetails.atRiskAppDetails.length === 0 + ) { + return; + } + + this.fileDownloadService.download({ + fileName: ExportHelper.getFileName("at-risk-applications"), + blobData: exportToCSV(this.drawerDetails.atRiskAppDetails, { + applicationName: this.i18nService.t("application"), + atRiskPasswordCount: this.i18nService.t("atRiskPasswords"), + }), + blobOptions: { type: "text/plain" }, + }); + } catch (error) { + // Log error for debugging + this.logService.error("Failed to download at-risk applications", error); + } + } } From a41c7b79b4e0b50e0c0916542aa3dcc7fdd42dc3 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 13 Nov 2025 13:33:56 -0600 Subject: [PATCH 121/249] [PM-20132] Total Member Count (#17330) * PM-20132 total member count * Apply suggestions from code review Co-authored-by: Leslie Tilton <23057410+Banrion@users.noreply.github.com> * PM-20132 updated PR comments * PM-20132 update as per PR comments * PM-20132 removed unwanted code * PM-20132 fixed PR comment from Claude * PM-20132 reduced ambiguity in code * PM-20132 removed unwanted observables * PM-20132 removed default value as it is not needed anymore * PM-20132 fixed failed test --------- Co-authored-by: Leslie Tilton <23057410+Banrion@users.noreply.github.com> --- .../risk-insights-orchestrator.service.ts | 25 ++++++++++++++++--- .../domain/risk-insights-report.service.ts | 4 +-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts index 59affad10da..38e12373182 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts @@ -42,6 +42,7 @@ import { createNewSummaryData, flattenMemberDetails, getTrimmedCipherUris, + getUniqueMembers, } from "../../helpers"; import { ApplicationHealthReportDetailEnriched, @@ -234,6 +235,7 @@ export class RiskInsightsOrchestratorService { const updatedSummaryData = this.reportService.getApplicationsSummary( report!.reportData, updatedApplicationData, + report!.summaryData.totalMemberCount, ); // Used for creating metrics with updated application data @@ -366,6 +368,7 @@ export class RiskInsightsOrchestratorService { const updatedSummaryData = this.reportService.getApplicationsSummary( report!.reportData, updatedApplicationData, + report!.summaryData.totalMemberCount, ); // Used for creating metrics with updated application data @@ -502,6 +505,7 @@ export class RiskInsightsOrchestratorService { const updatedSummaryData = this.reportService.getApplicationsSummary( report!.reportData, updatedApplicationData, + report!.summaryData.totalMemberCount, ); // Used for creating metrics with updated application data const manualEnrichedApplications = report!.reportData.map( @@ -656,19 +660,30 @@ export class RiskInsightsOrchestratorService { switchMap(([ciphers, memberCiphers]) => { this.logService.debug("[RiskInsightsOrchestratorService] Analyzing password health"); this._reportProgressSubject.next(ReportProgress.AnalyzingPasswords); - return this._getCipherHealth(ciphers ?? [], memberCiphers); + return forkJoin({ + memberDetails: of(memberCiphers), + cipherHealthReports: this._getCipherHealth(ciphers ?? [], memberCiphers), + }).pipe( + map(({ memberDetails, cipherHealthReports }) => { + const uniqueMembers = getUniqueMembers(memberDetails); + const totalMemberCount = uniqueMembers.length; + + return { cipherHealthReports, totalMemberCount }; + }), + ); }), - map((cipherHealthReports) => { + map(({ cipherHealthReports, totalMemberCount }) => { this.logService.debug("[RiskInsightsOrchestratorService] Calculating risk scores"); this._reportProgressSubject.next(ReportProgress.CalculatingRisks); - return this.reportService.generateApplicationsReport(cipherHealthReports); + const report = this.reportService.generateApplicationsReport(cipherHealthReports); + return { report, totalMemberCount }; }), tap(() => { this.logService.debug("[RiskInsightsOrchestratorService] Generating report data"); this._reportProgressSubject.next(ReportProgress.GeneratingReport); }), withLatestFrom(this.rawReportData$), - map(([report, previousReport]) => { + map(([{ report, totalMemberCount }, previousReport]) => { // Update the application data const updatedApplicationData = this.reportService.getOrganizationApplications( report, @@ -688,6 +703,7 @@ export class RiskInsightsOrchestratorService { const updatedSummary = this.reportService.getApplicationsSummary( report, updatedApplicationData, + totalMemberCount, ); // For now, merge the report with the critical marking flag to make the enriched type // We don't care about the individual ciphers in this instance @@ -964,6 +980,7 @@ export class RiskInsightsOrchestratorService { const summary = this.reportService.getApplicationsSummary( criticalApplications, enrichedReports.applicationData, + enrichedReports.summaryData.totalMemberCount, ); return { ...enrichedReports, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts index 94c9c85f955..37b788a8e3d 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-report.service.ts @@ -83,8 +83,8 @@ export class RiskInsightsReportService { getApplicationsSummary( reports: ApplicationHealthReportDetail[], applicationData: OrganizationReportApplication[], + totalMemberCount: number, ): OrganizationReportSummary { - const totalUniqueMembers = getUniqueMembers(reports.flatMap((x) => x.memberDetails)); const atRiskUniqueMembers = getUniqueMembers(reports.flatMap((x) => x.atRiskMemberDetails)); const criticalReports = this.filterApplicationsByCritical(reports, applicationData); @@ -94,7 +94,7 @@ export class RiskInsightsReportService { ); return { - totalMemberCount: totalUniqueMembers.length, + totalMemberCount: totalMemberCount, totalAtRiskMemberCount: atRiskUniqueMembers.length, totalApplicationCount: reports.length, totalAtRiskApplicationCount: reports.filter((app) => app.atRiskPasswordCount > 0).length, From e88720d4ed4cddfd16fa340bf2fecebeba5ad142 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 13 Nov 2025 13:35:03 -0600 Subject: [PATCH 122/249] PM-20961 App header added (#17350) --- .../app/dirt/access-intelligence/risk-insights.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 9dbfe582ac9..5e00de853ff 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -1,3 +1,5 @@ + + @let status = dataService.reportStatus$ | async; @let hasCiphers = dataService.hasCiphers$ | async; @@ -8,7 +10,6 @@ } @else { @if (isRiskInsightsActivityTabFeatureEnabled && !(dataService.hasReportData$ | async)) { -

{{ "accessIntelligence" | i18n }}

@if (!hasCiphers) { @@ -39,7 +40,6 @@
-

{{ "accessIntelligence" | i18n }}

{{ "reviewAtRiskPasswords" | i18n }}
From 35f35c43610a834b9d03166fa3ebe6dd8d8ea7b2 Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:06:56 -0600 Subject: [PATCH 123/249] [PM-26498] Add proofOfDecryption method to MasterPasswordUnlockService (#17322) * Add proofOfDecryption method to MasterPasswordUnlockService --- .../service-container/service-container.ts | 1 + .../src/services/jslib-services.module.ts | 2 +- .../master-password-unlock.service.ts | 14 +++ ...ult-master-password-unlock.service.spec.ts | 94 ++++++++++++++++++- .../default-master-password-unlock.service.ts | 40 ++++++++ 5 files changed, 148 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index c9f1d11210b..ebfb76eab2f 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -495,6 +495,7 @@ export class ServiceContainer { this.masterPasswordUnlockService = new DefaultMasterPasswordUnlockService( this.masterPasswordService, this.keyService, + this.logService, ); this.appIdService = new AppIdService(this.storageService, this.logService); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 97aa1869575..18c21024a6a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1087,7 +1087,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: MasterPasswordUnlockService, useClass: DefaultMasterPasswordUnlockService, - deps: [InternalMasterPasswordServiceAbstraction, KeyService], + deps: [InternalMasterPasswordServiceAbstraction, KeyService, LogService], }), safeProvider({ provide: KeyConnectorServiceAbstraction, diff --git a/libs/common/src/key-management/master-password/abstractions/master-password-unlock.service.ts b/libs/common/src/key-management/master-password/abstractions/master-password-unlock.service.ts index 4448206b2f6..63ee7d69719 100644 --- a/libs/common/src/key-management/master-password/abstractions/master-password-unlock.service.ts +++ b/libs/common/src/key-management/master-password/abstractions/master-password-unlock.service.ts @@ -7,7 +7,21 @@ export abstract class MasterPasswordUnlockService { * Unlocks the user's account using the master password. * @param masterPassword The master password provided by the user. * @param userId The ID of the active user. + * @throws If the master password provided is null/undefined/empty. + * @throws If the userId provided is null/undefined. + * @throws if the masterPasswordUnlockData for the user is not found. + * @throws If unwrapping the user key fails. * @returns the user's decrypted userKey. */ abstract unlockWithMasterPassword(masterPassword: string, userId: UserId): Promise; + + /** + * For the given master password and user ID, verifies whether the user can decrypt their user key stored in state. + * @param masterPassword The master password provided by the user. + * @param userId The ID of the active user. + * @throws If the master password provided is null/undefined/empty. + * @throws If the userId provided is null/undefined. + * @returns true if the userKey can be decrypted, false otherwise. + */ + abstract proofOfDecryption(masterPassword: string, userId: UserId): Promise; } diff --git a/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.spec.ts b/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.spec.ts index 75668e8e6bd..1c95090f04b 100644 --- a/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.spec.ts +++ b/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.spec.ts @@ -4,6 +4,8 @@ import { of } from "rxjs"; import { newGuid } from "@bitwarden/guid"; // eslint-disable-next-line no-restricted-imports import { Argon2KdfConfig, KeyService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; +import { CryptoError } from "@bitwarden/sdk-internal"; import { UserId } from "@bitwarden/user-core"; import { HashPurpose } from "../../../platform/enums"; @@ -23,6 +25,7 @@ describe("DefaultMasterPasswordUnlockService", () => { let masterPasswordService: MockProxy; let keyService: MockProxy; + let logService: MockProxy; const mockMasterPassword = "testExample"; const mockUserId = newGuid() as UserId; @@ -41,8 +44,9 @@ describe("DefaultMasterPasswordUnlockService", () => { beforeEach(() => { masterPasswordService = mock(); keyService = mock(); + logService = mock(); - sut = new DefaultMasterPasswordUnlockService(masterPasswordService, keyService); + sut = new DefaultMasterPasswordUnlockService(masterPasswordService, keyService, logService); masterPasswordService.masterPasswordUnlockData$.mockReturnValue( of(mockMasterPasswordUnlockData), @@ -73,7 +77,7 @@ describe("DefaultMasterPasswordUnlockService", () => { ); test.each([null as unknown as UserId, undefined as unknown as UserId])( - "throws when the provided master password is %s", + "throws when the provided userID is %s", async (userId) => { await expect(sut.unlockWithMasterPassword(mockMasterPassword, userId)).rejects.toThrow( "User ID is required", @@ -151,4 +155,90 @@ describe("DefaultMasterPasswordUnlockService", () => { expect(masterPasswordService.setMasterKey).not.toHaveBeenCalled(); }); }); + + describe("proofOfDecryption", () => { + test.each([null as unknown as string, undefined as unknown as string, ""])( + "throws when the provided master password is %s", + async (masterPassword) => { + await expect(sut.proofOfDecryption(masterPassword, mockUserId)).rejects.toThrow( + "Master password is required", + ); + expect(masterPasswordService.masterPasswordUnlockData$).not.toHaveBeenCalled(); + expect( + masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData, + ).not.toHaveBeenCalled(); + }, + ); + + test.each([null as unknown as UserId, undefined as unknown as UserId])( + "throws when the provided userID is %s", + async (userId) => { + await expect(sut.proofOfDecryption(mockMasterPassword, userId)).rejects.toThrow( + "User ID is required", + ); + }, + ); + + it("returns false when the user doesn't have masterPasswordUnlockData", async () => { + masterPasswordService.masterPasswordUnlockData$.mockReturnValue(of(null)); + + const result = await sut.proofOfDecryption(mockMasterPassword, mockUserId); + + expect(result).toBe(false); + expect(masterPasswordService.masterPasswordUnlockData$).toHaveBeenCalledWith(mockUserId); + expect( + masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData, + ).not.toHaveBeenCalled(); + expect(logService.warning).toHaveBeenCalledWith( + `[DefaultMasterPasswordUnlockService] No master password unlock data found for user ${mockUserId} returning false.`, + ); + }); + + it("returns true when the master password is correct", async () => { + const result = await sut.proofOfDecryption(mockMasterPassword, mockUserId); + + expect(result).toBe(true); + expect(masterPasswordService.masterPasswordUnlockData$).toHaveBeenCalledWith(mockUserId); + expect(masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData).toHaveBeenCalledWith( + mockMasterPassword, + mockMasterPasswordUnlockData, + ); + }); + + it("returns false when the master password is incorrect", async () => { + const error = new Error("Incorrect password") as CryptoError; + error.name = "CryptoError"; + error.variant = "InvalidKey"; + masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData.mockRejectedValue(error); + + const result = await sut.proofOfDecryption(mockMasterPassword, mockUserId); + + expect(result).toBe(false); + expect(masterPasswordService.masterPasswordUnlockData$).toHaveBeenCalledWith(mockUserId); + expect(masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData).toHaveBeenCalledWith( + mockMasterPassword, + mockMasterPasswordUnlockData, + ); + expect(logService.debug).toHaveBeenCalledWith( + `[DefaultMasterPasswordUnlockService] Error during proof of decryption for user ${mockUserId} returning false: ${error}`, + ); + }); + + it("returns false when a generic error occurs", async () => { + const error = new Error("Generic error"); + masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData.mockRejectedValue(error); + + const result = await sut.proofOfDecryption(mockMasterPassword, mockUserId); + + expect(result).toBe(false); + expect(masterPasswordService.masterPasswordUnlockData$).toHaveBeenCalledWith(mockUserId); + expect(masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData).toHaveBeenCalledWith( + mockMasterPassword, + mockMasterPasswordUnlockData, + ); + expect(logService.error).toHaveBeenCalledWith( + `[DefaultMasterPasswordUnlockService] Unexpected error during proof of decryption for user ${mockUserId} returning false: ${error}`, + ); + }); + }); }); diff --git a/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.ts b/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.ts index 87114000abf..89a87403e49 100644 --- a/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.ts +++ b/libs/common/src/key-management/master-password/services/default-master-password-unlock.service.ts @@ -2,6 +2,8 @@ import { firstValueFrom } from "rxjs"; // eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; +import { LogService } from "@bitwarden/logging"; +import { isCryptoError } from "@bitwarden/sdk-internal"; import { UserId } from "@bitwarden/user-core"; import { HashPurpose } from "../../../platform/enums"; @@ -14,6 +16,7 @@ export class DefaultMasterPasswordUnlockService implements MasterPasswordUnlockS constructor( private readonly masterPasswordService: InternalMasterPasswordServiceAbstraction, private readonly keyService: KeyService, + private readonly logService: LogService, ) {} async unlockWithMasterPassword(masterPassword: string, userId: UserId): Promise { @@ -37,6 +40,43 @@ export class DefaultMasterPasswordUnlockService implements MasterPasswordUnlockS return userKey; } + async proofOfDecryption(masterPassword: string, userId: UserId): Promise { + this.validateInput(masterPassword, userId); + + try { + const masterPasswordUnlockData = await firstValueFrom( + this.masterPasswordService.masterPasswordUnlockData$(userId), + ); + + if (masterPasswordUnlockData == null) { + this.logService.warning( + `[DefaultMasterPasswordUnlockService] No master password unlock data found for user ${userId} returning false.`, + ); + return false; + } + + const userKey = await this.masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData( + masterPassword, + masterPasswordUnlockData, + ); + + return userKey != null; + } catch (error) { + // masterPasswordService.unwrapUserKeyFromMasterPasswordUnlockData is expected to throw if the password is incorrect. + // Currently this throws CryptoError:InvalidKey if decrypting the user key fails at all. + if (isCryptoError(error) && error.variant === "InvalidKey") { + this.logService.debug( + `[DefaultMasterPasswordUnlockService] Error during proof of decryption for user ${userId} returning false: ${error}`, + ); + } else { + this.logService.error( + `[DefaultMasterPasswordUnlockService] Unexpected error during proof of decryption for user ${userId} returning false: ${error}`, + ); + } + return false; + } + } + private validateInput(masterPassword: string, userId: UserId): void { if (masterPassword == null || masterPassword === "") { throw new Error("Master password is required"); From 9586057a328a6f3ec34cab34f0faa0e47ba2e39c Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:14:45 +0100 Subject: [PATCH 124/249] [PM-26162] [Chromium importer] Add fallback name in case empty when loading browser profiles (#16664) * Added a fallback to use a browser profile folder-name in case the name of the profile is empty --- .../chromium_importer/src/chromium/mod.rs | 125 +++++++++++++++++- 1 file changed, 119 insertions(+), 6 deletions(-) diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index aec8a84b5c1..369e63e0ad1 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::LazyLock; @@ -148,13 +149,13 @@ pub(crate) struct LocalState { #[derive(serde::Deserialize, Clone)] struct AllProfiles { - info_cache: std::collections::HashMap, + info_cache: HashMap, } #[derive(serde::Deserialize, Clone)] struct OneProfile { name: String, - gaia_name: Option, + gaia_id: Option, user_name: Option, } @@ -197,10 +198,14 @@ fn get_profile_info(local_state: &LocalState) -> Vec { .profile .info_cache .iter() - .map(|(name, info)| ProfileInfo { - name: info.name.clone(), - folder: name.clone(), - account_name: info.gaia_name.clone(), + .map(|(folder, info)| ProfileInfo { + name: if !info.name.trim().is_empty() { + info.name.clone() + } else { + folder.clone() + }, + folder: folder.clone(), + account_name: info.gaia_id.clone(), account_email: info.user_name.clone(), }) .collect() @@ -348,3 +353,111 @@ async fn decrypt_login( }), } } + +#[cfg(test)] +mod tests { + use super::*; + + fn make_local_state(profiles: Vec<(&str, &str, Option<&str>, Option<&str>)>) -> LocalState { + let info_cache = profiles + .into_iter() + .map(|(folder, name, gaia_id, user_name)| { + ( + folder.to_string(), + OneProfile { + name: name.to_string(), + gaia_id: gaia_id.map(|s| s.to_string()), + user_name: user_name.map(|s| s.to_string()), + }, + ) + }) + .collect::>(); + + LocalState { + profile: AllProfiles { info_cache }, + os_crypt: None, + } + } + + #[test] + fn test_get_profile_info_basic() { + let local_state = make_local_state(vec![ + ( + "Profile 1", + "User 1", + Some("Account 1"), + Some("email1@example.com"), + ), + ( + "Profile 2", + "User 2", + Some("Account 2"), + Some("email2@example.com"), + ), + ]); + let infos = get_profile_info(&local_state); + assert_eq!(infos.len(), 2); + + let profile1 = infos.iter().find(|p| p.folder == "Profile 1").unwrap(); + assert_eq!(profile1.name, "User 1"); + assert_eq!(profile1.account_name.as_deref(), Some("Account 1")); + assert_eq!( + profile1.account_email.as_deref(), + Some("email1@example.com") + ); + + let profile2 = infos.iter().find(|p| p.folder == "Profile 2").unwrap(); + assert_eq!(profile2.name, "User 2"); + assert_eq!(profile2.account_name.as_deref(), Some("Account 2")); + assert_eq!( + profile2.account_email.as_deref(), + Some("email2@example.com") + ); + } + + #[test] + fn test_get_profile_info_empty_name() { + let local_state = make_local_state(vec![( + "ProfileX", + "", + Some("AccountX"), + Some("emailx@example.com"), + )]); + let infos = get_profile_info(&local_state); + assert_eq!(infos.len(), 1); + assert_eq!(infos[0].name, "ProfileX"); + assert_eq!(infos[0].folder, "ProfileX"); + } + + #[test] + fn test_get_profile_info_none_fields() { + let local_state = make_local_state(vec![("ProfileY", "NameY", None, None)]); + let infos = get_profile_info(&local_state); + assert_eq!(infos.len(), 1); + assert_eq!(infos[0].name, "NameY"); + assert_eq!(infos[0].account_name, None); + assert_eq!(infos[0].account_email, None); + } + + #[test] + fn test_get_profile_info_multiple_profiles() { + let local_state = make_local_state(vec![ + ("P1", "N1", Some("A1"), Some("E1")), + ("P2", "", None, None), + ("P3", "N3", Some("A3"), None), + ]); + let infos = get_profile_info(&local_state); + assert_eq!(infos.len(), 3); + + let p1 = infos.iter().find(|p| p.folder == "P1").unwrap(); + assert_eq!(p1.name, "N1"); + + let p2 = infos.iter().find(|p| p.folder == "P2").unwrap(); + assert_eq!(p2.name, "P2"); + + let p3 = infos.iter().find(|p| p.folder == "P3").unwrap(); + assert_eq!(p3.name, "N3"); + assert_eq!(p3.account_name.as_deref(), Some("A3")); + assert_eq!(p3.account_email, None); + } +} From ccf7bb1753ec7cea7cd79750c71e6099ef2b3209 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Thu, 13 Nov 2025 15:53:05 -0500 Subject: [PATCH 125/249] [CL-736] migrate chip select to use signals (#17136) * migrate chip select to use signals * Have Claude address feedback and create spec file * remove eslint disable comment * fix failing tests * remove unnecessary tests * improved documentation * remove unnecessary test logic * consolidate tests and remove fragile selectors --- .../chip-select/chip-select.component.html | 18 +- .../chip-select/chip-select.component.spec.ts | 486 ++++++++++++++++++ .../src/chip-select/chip-select.component.ts | 97 ++-- 3 files changed, 545 insertions(+), 56 deletions(-) create mode 100644 libs/components/src/chip-select/chip-select.component.spec.ts diff --git a/libs/components/src/chip-select/chip-select.component.html b/libs/components/src/chip-select/chip-select.component.html index d43e09e8338..ee252b35fe9 100644 --- a/libs/components/src/chip-select/chip-select.component.html +++ b/libs/components/src/chip-select/chip-select.component.html @@ -3,11 +3,11 @@ class="tw-inline-flex tw-items-center tw-rounded-full tw-w-full tw-border-solid tw-border tw-gap-1.5 tw-group/chip-select" [ngClass]="{ 'tw-bg-text-muted hover:tw-bg-secondary-700 tw-text-contrast hover:!tw-border-secondary-700': - selectedOption && !disabled, + selectedOption && !disabled(), 'tw-bg-transparent hover:tw-border-secondary-700 !tw-text-muted hover:tw-bg-secondary-100': - !selectedOption && !disabled, - 'tw-bg-secondary-300 tw-text-muted tw-border-transparent': disabled, - 'tw-border-text-muted': !disabled, + !selectedOption && !disabled(), + 'tw-bg-secondary-300 tw-text-muted tw-border-transparent': disabled(), + 'tw-border-text-muted': !disabled(), 'tw-ring-2 tw-ring-primary-600 tw-ring-offset-1': focusVisibleWithin(), }" > @@ -17,11 +17,11 @@ class="tw-inline-flex tw-gap-1.5 tw-items-center tw-justify-between tw-bg-transparent hover:tw-bg-transparent tw-border-none tw-outline-none tw-w-full tw-py-1 tw-ps-3 last:tw-pe-3 [&:not(:last-child)]:tw-pe-0 tw-truncate tw-text-[color:inherit] tw-text-[length:inherit]" data-fvw-target [ngClass]="{ - 'tw-cursor-not-allowed': disabled, - 'group-hover/chip-select:tw-text-secondary-700': !selectedOption && !disabled, + 'tw-cursor-not-allowed': disabled(), + 'group-hover/chip-select:tw-text-secondary-700': !selectedOption && !disabled(), }" [bitMenuTriggerFor]="menu" - [disabled]="disabled" + [disabled]="disabled()" [title]="label" #menuTrigger="menuTrigger" (click)="setMenuWidth()" @@ -45,10 +45,10 @@ - * click button to hide this content - * ``` - * + * The `bit-disclosure` component is used in tandem with the `bitDisclosureTriggerFor` directive to create an accessible content area whose visibility is controlled by a trigger button. + * + * To compose a disclosure and trigger: + * + * 1. Create a trigger component (see "Supported Trigger Components" section below) + * 2. Create a `bit-disclosure` + * 3. Set a template reference on the `bit-disclosure` + * 4. Use the `bitDisclosureTriggerFor` directive on the trigger component, and pass it the `bit-disclosure` template reference + * 5. Set the `open` property on the `bit-disclosure` to init the disclosure as either currently expanded or currently collapsed. The disclosure will default to `false`, meaning it defaults to being hidden. + * + * @example + * + * ```html + * + * click button to hide this content + * ``` */ -// 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-disclosure", template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + "[class]": "classList()", + "[id]": "id", + }, }) export class DisclosureComponent { - /** Emits the visibility of the disclosure content */ - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() openChange = new EventEmitter(); - - private _open?: boolean; /** - * Optionally init the disclosure in its opened state + * Controls the visibility of the disclosure content. */ - // TODO: Skipped for signal migration because: - // Accessor inputs cannot be migrated as they are too complex. - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input({ transform: booleanAttribute }) set open(isOpen: boolean) { - this._open = isOpen; - this.openChange.emit(isOpen); - } - get open(): boolean { - return !!this._open; - } + readonly open = model(false); - @HostBinding("class") get classList() { - return this.open ? "" : "tw-hidden"; - } + /** + * Autogenerated id. + */ + readonly id = `bit-disclosure-${nextId++}`; - @HostBinding("id") id = `bit-disclosure-${nextId++}`; + protected readonly classList = computed(() => (this.open() ? "" : "tw-hidden")); } diff --git a/libs/components/src/disclosure/disclosure.mdx b/libs/components/src/disclosure/disclosure.mdx index 50ccf936acc..9f01df8d3d9 100644 --- a/libs/components/src/disclosure/disclosure.mdx +++ b/libs/components/src/disclosure/disclosure.mdx @@ -11,7 +11,7 @@ import { DisclosureComponent, DisclosureTriggerForDirective } from "@bitwarden/c <Description /> -<Canvas of={stories.DisclosureWithIconButton} /> +<Canvas of={stories.DisclosureOpen} /> ## Supported Trigger Components diff --git a/libs/components/src/disclosure/disclosure.stories.ts b/libs/components/src/disclosure/disclosure.stories.ts index 3ed6903060c..cd9d9e02360 100644 --- a/libs/components/src/disclosure/disclosure.stories.ts +++ b/libs/components/src/disclosure/disclosure.stories.ts @@ -36,13 +36,30 @@ export default { type Story = StoryObj<DisclosureComponent>; -export const DisclosureWithIconButton: Story = { +export const DisclosureOpen: Story = { + args: { + open: true, + }, render: (args) => ({ props: args, template: /*html*/ ` - <button type="button" label="Settings" bitIconButton="bwi-sliders" [buttonType]="'muted'" [bitDisclosureTriggerFor]="disclosureRef"> + <button type="button" label="Settings" bitIconButton="bwi-sliders" buttonType="muted" [bitDisclosureTriggerFor]="disclosureRef"> </button> - <bit-disclosure #disclosureRef class="tw-text-main tw-block" open>click button to hide this content</bit-disclosure> + <bit-disclosure #disclosureRef class="tw-text-main tw-block" [(open)]="open">click button to hide this content</bit-disclosure> + `, + }), +}; + +export const DisclosureClosed: Story = { + args: { + open: false, + }, + render: (args) => ({ + props: args, + template: /*html*/ ` + <button type="button" label="Settings" bitIconButton="bwi-sliders" buttonType="muted" [bitDisclosureTriggerFor]="disclosureRef"> + </button> + <bit-disclosure #disclosureRef class="tw-text-main tw-block" [(open)]="open">click button to hide this content</bit-disclosure> `, }), }; From 9a3ba9e05b4199d28de12b68ae9bd291be1494ef Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Thu, 13 Nov 2025 21:56:37 -0500 Subject: [PATCH 128/249] Fix workflow formatting (#17382) --- .github/workflows/publish-cli.yml | 22 ++++++----- .github/workflows/publish-desktop.yml | 53 +++++++++------------------ .github/workflows/release-browser.yml | 10 ++--- .github/workflows/release-desktop.yml | 6 +-- .github/workflows/release-web.yml | 9 ++--- 5 files changed, 42 insertions(+), 58 deletions(-) diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index bcae79d077e..08d3f1de503 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -66,15 +66,17 @@ jobs: - name: Version output id: version-output env: - _INPUT_VERSION: ${{ inputs.version }} + INPUT_VERSION: ${{ inputs.version }} run: | - if [[ "$_INPUT_VERSION" == "latest" || "$_INPUT_VERSION" == "" ]]; then - VERSION=$(curl "https://api.github.com/repos/bitwarden/clients/releases" | jq -c '.[] | select(.tag_name | contains("cli")) | .tag_name' | head -1 | grep -ohE '20[0-9]{2}\.([1-9]|1[0-2])\.[0-9]+') + if [[ "$INPUT_VERSION" == "latest" || "$INPUT_VERSION" == "" ]]; then + TAG_NAME=$(curl -s "https://api.github.com/repos/bitwarden/clients/releases" \ + | jq -r '.[] | select(.tag_name | contains("cli")) | .tag_name' | head -1) + VERSION="${TAG_NAME#cli-v}" echo "Latest Released Version: $VERSION" echo "version=$VERSION" >> "$GITHUB_OUTPUT" else - echo "Release Version: $_INPUT_VERSION" - echo "version=$_INPUT_VERSION" >> "$GITHUB_OUTPUT" + echo "Release Version: $INPUT_VERSION" + echo "version=$INPUT_VERSION" >> "$GITHUB_OUTPUT" fi - name: Create GitHub deployment @@ -126,14 +128,14 @@ jobs: uses: samuelmeuli/action-snapcraft@fceeb3c308e76f3487e72ef608618de625fb7fe8 # v3.0.1 - name: Download artifacts - run: wget "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bw_$_PKG_VERSION_amd64.snap" + run: wget "https://github.com/bitwarden/clients/releases/download/cli-v${_PKG_VERSION}/bw_${_PKG_VERSION}_amd64.snap" - name: Publish Snap & logout if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | - snapcraft upload "bw_$_PKG_VERSION_amd64.snap" --release stable + snapcraft upload "bw_${_PKG_VERSION}_amd64.snap" --release stable snapcraft logout choco: @@ -179,7 +181,7 @@ jobs: run: New-Item -ItemType directory -Path ./dist - name: Download artifacts - run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bitwarden-cli.$_PKG_VERSION.nupkg" -OutFile bitwarden-cli.$_PKG_VERSION.nupkg + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/cli-v$($env:_PKG_VERSION)/bitwarden-cli.$($env:_PKG_VERSION).nupkg" -OutFile bitwarden-cli.$($env:_PKG_VERSION).nupkg working-directory: apps/cli/dist - name: Push to Chocolatey @@ -227,8 +229,8 @@ jobs: - name: Download and set up artifact run: | mkdir -p build - wget "https://github.com/bitwarden/clients/releases/download/cli-v$_PKG_VERSION/bitwarden-cli-$_PKG_VERSION-npm-build.zip" - unzip "bitwarden-cli-$_PKG_VERSION-npm-build.zip" -d build + wget "https://github.com/bitwarden/clients/releases/download/cli-v${_PKG_VERSION}/bitwarden-cli-${_PKG_VERSION}-npm-build.zip" + unzip "bitwarden-cli-${_PKG_VERSION}-npm-build.zip" -d build - name: Publish NPM if: ${{ inputs.publish_type != 'Dry Run' }} diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index 15a0ec77d5b..f42f9811d77 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -73,12 +73,11 @@ jobs: - name: Check Publish Version id: version env: - _INPUT_VERSION: ${{ inputs.version }} + INPUT_VERSION: ${{ inputs.version }} run: | - if [[ "$_INPUT_VERSION" == "latest" || "$_INPUT_VERSION" == "" ]]; then - TAG_NAME=$(curl "https://api.github.com/repos/bitwarden/clients/releases" \ - | jq -c '.[] | select(.tag_name | contains("desktop")) | .tag_name' \ - | head -1 | cut -d '"' -f 2) + if [[ "$INPUT_VERSION" == "latest" || "$INPUT_VERSION" == "" ]]; then + TAG_NAME=$(curl -s "https://api.github.com/repos/bitwarden/clients/releases" \ + | jq -r '.[] | select(.tag_name | contains("desktop")) | .tag_name' | head -1) VERSION="${TAG_NAME#desktop-v}" echo "Latest Released Version: $VERSION" @@ -87,7 +86,7 @@ jobs: echo "Tag name: $TAG_NAME" echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" else - VERSION="$_INPUT_VERSION" + VERSION="$INPUT_VERSION" TAG_NAME="desktop-v$VERSION" echo "Release Version: $VERSION" @@ -100,9 +99,9 @@ jobs: - name: Get Version Channel id: release_channel env: - _VERSION: ${{ steps.version.outputs.version }} + VERSION: ${{ steps.version.outputs.version }} run: | - case "${_VERSION}" in + case "${VERSION}" in *"alpha"*) echo "channel=alpha" >> "$GITHUB_OUTPUT" echo "[!] We do not yet support 'alpha'" @@ -192,22 +191,6 @@ jobs: --recursive \ --quiet - - name: Update deployment status to Success - if: ${{ inputs.publish_type != 'Dry Run' && success() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'success' - deployment-id: ${{ needs.setup.outputs.deployment_id }} - - - name: Update deployment status to Failure - if: ${{ inputs.publish_type != 'Dry Run' && failure() }} - uses: chrnorm/deployment-status@9a72af4586197112e0491ea843682b5dc280d806 # v2.0.3 - with: - token: '${{ secrets.GITHUB_TOKEN }}' - state: 'failure' - deployment-id: ${{ needs.setup.outputs.deployment_id }} - snap: name: Deploy Snap runs-on: ubuntu-22.04 @@ -251,14 +234,14 @@ jobs: - name: Download artifacts working-directory: apps/desktop/dist - run: wget "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/bitwarden_$_PKG_VERSION_amd64.snap" + run: wget "https://github.com/bitwarden/clients/releases/download/${_RELEASE_TAG}/bitwarden_${_PKG_VERSION}_amd64.snap" - name: Deploy to Snap Store if: ${{ inputs.publish_type != 'Dry Run' }} env: SNAPCRAFT_STORE_CREDENTIALS: ${{ steps.retrieve-secrets.outputs.snapcraft-store-token }} run: | - snapcraft upload "bitwarden_$_PKG_VERSION_amd64.snap" --release stable + snapcraft upload "bitwarden_${_PKG_VERSION}_amd64.snap" --release stable snapcraft logout working-directory: apps/desktop/dist @@ -312,7 +295,7 @@ jobs: - name: Download artifacts working-directory: apps/desktop/dist - run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/bitwarden.$_PKG_VERSION.nupkg" -OutFile "bitwarden.$_PKG_VERSION.nupkg" + run: Invoke-WebRequest -Uri "https://github.com/bitwarden/clients/releases/download/$($env:_RELEASE_TAG)/bitwarden.$($env:_PKG_VERSION).nupkg" -OutFile "bitwarden.$($env:_PKG_VERSION).nupkg" - name: Push to Chocolatey if: ${{ inputs.publish_type != 'Dry Run' }} @@ -337,7 +320,7 @@ jobs: persist-credentials: false - name: Validate release notes for MAS - if: inputs.mas_publish && (inputs.release_notes == '' || inputs.release_notes == null) + if: inputs.release_notes == '' || inputs.release_notes == null run: | echo "❌ Release notes are required when publishing to Mac App Store" echo "Please provide release notes using the 'Release Notes' input field" @@ -345,7 +328,7 @@ jobs: - name: Download MacOS App Store build number working-directory: apps/desktop - run: wget "https://github.com/bitwarden/clients/releases/download/$_RELEASE_TAG/macos-build-number.json" + run: wget "https://github.com/bitwarden/clients/releases/download/${_RELEASE_TAG}/macos-build-number.json" - name: Setup Ruby and Install Fastlane uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 @@ -379,20 +362,20 @@ jobs: env: APP_STORE_CONNECT_TEAM_ISSUER: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-TEAM-ISSUER }} APP_STORE_CONNECT_AUTH_KEY: ${{ steps.get-kv-secrets.outputs.APP-STORE-CONNECT-AUTH-KEY }} - _RELEASE_NOTES: ${{ inputs.release_notes }} - _PUBLISH_TYPE: ${{ inputs.publish_type }} + CHANGELOG: ${{ inputs.release_notes }} + PUBLISH_TYPE: ${{ inputs.publish_type }} working-directory: apps/desktop run: | BUILD_NUMBER=$(jq -r '.buildNumber' macos-build-number.json) - CHANGELOG="$_RELEASE_NOTES" - IS_DRY_RUN="$_PUBLISH_TYPE == 'Dry Run'" - if [ "$IS_DRY_RUN" = "true" ]; then + if [ "$PUBLISH_TYPE" = "Dry Run" ]; then echo "🧪 DRY RUN MODE - Testing without actual App Store submission" echo "📦 Would publish build $BUILD_NUMBER to Mac App Store" + IS_DRY_RUN="true" else echo "🚀 PRODUCTION MODE - Publishing to Mac App Store" echo "📦 Publishing build $BUILD_NUMBER to Mac App Store" + IS_DRY_RUN="false" fi echo "📝 Release notes (${#CHANGELOG} chars): ${CHANGELOG:0:100}..." @@ -404,7 +387,7 @@ jobs: fi fastlane publish --verbose \ - app_version:"$PKG_VERSION" \ + app_version:"${_PKG_VERSION}" \ build_number:"$BUILD_NUMBER" \ changelog:"$CHANGELOG" \ dry_run:"$IS_DRY_RUN" diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index c7faefb2ce9..53382539b89 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -132,11 +132,11 @@ jobs: env: PACKAGE_VERSION: ${{ needs.setup.outputs.release_version }} run: | - mv browser-source.zip "browser-source-$PACKAGE_VERSION.zip" - mv dist-chrome.zip "dist-chrome-$PACKAGE_VERSION.zip" - mv dist-opera.zip "dist-opera-$PACKAGE_VERSION.zip" - mv dist-firefox.zip "dist-firefox-$PACKAGE_VERSION.zip" - mv dist-edge.zip "dist-edge-$PACKAGE_VERSION.zip" + mv browser-source.zip "browser-source-${PACKAGE_VERSION}.zip" + mv dist-chrome.zip "dist-chrome-${PACKAGE_VERSION}.zip" + mv dist-opera.zip "dist-opera-${PACKAGE_VERSION}.zip" + mv dist-firefox.zip "dist-firefox-${PACKAGE_VERSION}.zip" + mv dist-edge.zip "dist-edge-${PACKAGE_VERSION}.zip" - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 35fc8bed8a9..53132d8647c 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -58,9 +58,9 @@ jobs: - name: Get Version Channel id: release_channel env: - _VERSION: ${{ steps.version.outputs.version }} + VERSION: ${{ steps.version.outputs.version }} run: | - case "$_VERSION" in + case "$VERSION" in *"alpha"*) echo "channel=alpha" >> "$GITHUB_OUTPUT" echo "[!] We do not yet support 'alpha'" @@ -96,7 +96,7 @@ jobs: env: PKG_VERSION: ${{ steps.version.outputs.version }} working-directory: apps/desktop/artifacts - run: mv "Bitwarden-$PKG_VERSION-universal.pkg" "Bitwarden-$PKG_VERSION-universal.pkg.archive" + run: mv "Bitwarden-${PKG_VERSION}-universal.pkg" "Bitwarden-${PKG_VERSION}-universal.pkg.archive" - name: Create Release uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0 diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 59022657398..9203769bc77 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -52,8 +52,7 @@ jobs: release: name: Create GitHub Release runs-on: ubuntu-22.04 - needs: - - setup + needs: setup permissions: contents: write steps: @@ -82,10 +81,10 @@ jobs: - name: Rename assets working-directory: apps/web/artifacts env: - _RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} + RELEASE_VERSION: ${{ needs.setup.outputs.release_version }} run: | - mv web-*-selfhosted-COMMERCIAL.zip "web-$_RELEASE_VERSION-selfhosted-COMMERCIAL.zip" - mv web-*-selfhosted-open-source.zip "web-$_RELEASE_VERSION-selfhosted-open-source.zip" + mv web-*-selfhosted-COMMERCIAL.zip "web-${RELEASE_VERSION}-selfhosted-COMMERCIAL.zip" + mv web-*-selfhosted-open-source.zip "web-${RELEASE_VERSION}-selfhosted-open-source.zip" - name: Create release if: ${{ github.event.inputs.release_type != 'Dry Run' }} From a55d0f02f23220e446cd38a48fa399806f8e9cb7 Mon Sep 17 00:00:00 2001 From: Mark Youssef <141061617+mark-youssef-bitwarden@users.noreply.github.com> Date: Thu, 13 Nov 2025 18:59:03 -0800 Subject: [PATCH 129/249] [CL-672] update mobile design of dialog (#14828) --------- Co-authored-by: Vicki League <vleague@bitwarden.com> --- .../await-desktop-dialog.component.ts | 11 ++- ...ktop-sync-verification-dialog.component.ts | 2 + ...-file-popout-dialog-container.component.ts | 6 +- .../about-page/about-page-v2.component.ts | 6 +- .../at-risk-carousel-dialog.component.ts | 2 + ...wser-sync-verification-dialog.component.ts | 9 ++- ...erify-native-messaging-dialog.component.ts | 9 ++- ...t-organization-data-ownership.component.ts | 6 +- .../access-selector-dialog.stories.ts | 2 +- .../navigation-switcher.component.spec.ts | 2 +- .../setup-extension.component.ts | 2 + .../vault-item-dialog.component.ts | 2 + .../bulk-delete-dialog.component.ts | 6 +- .../overview/overview.component.ts | 3 +- .../project/project-secrets.component.ts | 3 +- .../secrets/dialog/secret-dialog.component.ts | 2 + .../secrets/secrets.component.ts | 8 ++- .../secrets-manager/trash/trash.component.ts | 4 +- .../premium-upgrade-dialog.component.ts | 5 +- .../fingerprint-dialog.component.ts | 13 +++- libs/components/src/dialog/dialog.service.ts | 67 ++++++++++++++++++- .../src/dialog/dialog/dialog.component.html | 6 +- .../src/dialog/dialog/dialog.component.ts | 43 ++++++++---- .../src/dialog/dialog/dialog.stories.ts | 15 +++-- .../simple-dialog.service.stories.ts | 5 +- .../src/navigation/side-nav.service.ts | 20 +++--- libs/components/src/utils/responsive-utils.ts | 27 ++++++++ libs/components/tailwind.config.base.js | 14 ++++ .../advanced-uri-option-dialog.component.ts | 2 + .../decryption-failure-dialog.component.ts | 6 +- 30 files changed, 255 insertions(+), 53 deletions(-) create mode 100644 libs/components/src/utils/responsive-utils.ts diff --git a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts index a64cea1ef3e..12cf669d89b 100644 --- a/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts +++ b/apps/browser/src/auth/popup/settings/await-desktop-dialog.component.ts @@ -1,7 +1,12 @@ import { Component } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { + ButtonModule, + CenterPositionStrategy, + DialogModule, + DialogService, +} from "@bitwarden/components"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -11,6 +16,8 @@ import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components }) export class AwaitDesktopDialogComponent { static open(dialogService: DialogService) { - return dialogService.open<boolean>(AwaitDesktopDialogComponent); + return dialogService.open<boolean>(AwaitDesktopDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + }); } } diff --git a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts index 510348927ce..e1774dbbddd 100644 --- a/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts +++ b/apps/browser/src/popup/components/desktop-sync-verification-dialog.component.ts @@ -9,6 +9,7 @@ import { ButtonModule, DialogModule, DialogService, + CenterPositionStrategy, } from "@bitwarden/components"; export type DesktopSyncVerificationDialogParams = { @@ -49,6 +50,7 @@ export class DesktopSyncVerificationDialogComponent implements OnDestroy, OnInit static open(dialogService: DialogService, data: DesktopSyncVerificationDialogParams) { return dialogService.open(DesktopSyncVerificationDialogComponent, { data, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts index 56b8bcbb9f5..1f0d9f2a0c9 100644 --- a/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-file-popout-dialog/send-file-popout-dialog-container.component.ts @@ -3,7 +3,7 @@ import { Component, input, OnInit } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { DialogService } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService } from "@bitwarden/components"; import { SendFormConfig } from "@bitwarden/send-ui"; import { FilePopoutUtilsService } from "../../services/file-popout-utils.service"; @@ -33,7 +33,9 @@ export class SendFilePopoutDialogContainerComponent implements OnInit { this.config().mode === "add" && this.filePopoutUtilsService.showFilePopoutMessage(window) ) { - this.dialogService.open(SendFilePopoutDialogComponent); + this.dialogService.open(SendFilePopoutDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + }); } } } diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts index 2ef830d9d94..88f6ad96807 100644 --- a/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts +++ b/apps/browser/src/tools/popup/settings/about-page/about-page-v2.component.ts @@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DeviceType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService, ItemModule } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService, ItemModule } from "@bitwarden/components"; import { BrowserApi } from "../../../../platform/browser/browser-api"; import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; @@ -51,7 +51,9 @@ export class AboutPageV2Component { ) {} about() { - this.dialogService.open(AboutDialogComponent); + this.dialogService.open(AboutDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + }); } async launchHelp() { diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts index f81bccc760c..1b83c316f41 100644 --- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -7,6 +7,7 @@ import { DialogModule, DialogService, TypographyModule, + CenterPositionStrategy, } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; import { DarkImageSourceDirective, VaultCarouselModule } from "@bitwarden/vault"; @@ -52,6 +53,7 @@ export class AtRiskCarouselDialogComponent { static open(dialogService: DialogService) { return dialogService.open<AtRiskCarouselDialogResult>(AtRiskCarouselDialogComponent, { disableClose: true, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts b/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts index 5d3c777f333..d65df60a8ce 100644 --- a/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts +++ b/apps/desktop/src/app/components/browser-sync-verification-dialog.component.ts @@ -1,7 +1,13 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { DIALOG_DATA, ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { + DIALOG_DATA, + ButtonModule, + DialogModule, + DialogService, + CenterPositionStrategy, +} from "@bitwarden/components"; export type BrowserSyncVerificationDialogParams = { fingerprint: string[]; @@ -19,6 +25,7 @@ export class BrowserSyncVerificationDialogComponent { static open(dialogService: DialogService, data: BrowserSyncVerificationDialogParams) { return dialogService.open(BrowserSyncVerificationDialogComponent, { data, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts b/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts index 14c2b137d73..6f9695f856a 100644 --- a/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts +++ b/apps/desktop/src/app/components/verify-native-messaging-dialog.component.ts @@ -1,7 +1,13 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { DIALOG_DATA, ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { + DIALOG_DATA, + ButtonModule, + DialogModule, + DialogService, + CenterPositionStrategy, +} from "@bitwarden/components"; export type VerifyNativeMessagingDialogData = { applicationName: string; @@ -19,6 +25,7 @@ export class VerifyNativeMessagingDialogComponent { static open(dialogService: DialogService, data: VerifyNativeMessagingDialogData) { return dialogService.open<boolean>(VerifyNativeMessagingDialogComponent, { data, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts index a15c51ebf70..a0d425d5886 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.ts @@ -9,7 +9,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrgKey } from "@bitwarden/common/types/key"; -import { DialogService } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService } from "@bitwarden/components"; import { EncString } from "@bitwarden/sdk-internal"; import { SharedModule } from "../../../../shared"; @@ -58,7 +58,9 @@ export class vNextOrganizationDataOwnershipPolicyComponent override async confirm(): Promise<boolean> { if (this.policyResponse?.enabled && !this.enabled.value) { - const dialogRef = this.dialogService.open(this.warningContent); + const dialogRef = this.dialogService.open(this.warningContent, { + positionStrategy: new CenterPositionStrategy(), + }); const result = await lastValueFrom(dialogRef.closed); return Boolean(result); } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts index 5cb61197b99..3e23eff13a9 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts @@ -25,7 +25,7 @@ const render: Story["render"] = (args) => ({ ...args, }, template: ` - <bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding"> + <bit-dialog [dialogSize]="dialogSize" [disablePadding]="disablePadding" disableAnimations> <span bitDialogTitle>Access selector</span> <span bitDialogContent> <bit-access-selector diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index 873b306a450..9f6c8f6b194 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -28,7 +28,7 @@ class MockUpgradeNavButtonComponent {} Object.defineProperty(window, "matchMedia", { writable: true, value: jest.fn().mockImplementation((query) => ({ - matches: false, + matches: true, media: query, onchange: null, addListener: jest.fn(), // deprecated diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts index b5c0d096944..974e73bc91e 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts @@ -16,6 +16,7 @@ import { getWebStoreUrl } from "@bitwarden/common/vault/utils/get-web-store-url" import { AnonLayoutWrapperDataService, ButtonComponent, + CenterPositionStrategy, DialogRef, DialogService, IconModule, @@ -151,6 +152,7 @@ export class SetupExtensionComponent implements OnInit, OnDestroy { data: { onDismiss: this.dismissExtensionPage.bind(this), }, + positionStrategy: new CenterPositionStrategy(), }, ); } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 98922fb114f..8508596a67b 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -48,6 +48,7 @@ import { DialogService, ItemModule, ToastService, + CenterPositionStrategy, } from "@bitwarden/components"; import { AttachmentDialogCloseResult, @@ -331,6 +332,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { if (this.cipher.decryptionFailure) { this.dialogService.open(DecryptionFailureDialogComponent, { data: { cipherIds: [this.cipher.id] }, + positionStrategy: new CenterPositionStrategy(), }); this.dialogRef.close(); return; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 3856bb65324..5f139ade144 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -14,6 +14,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherBulkDeleteRequest } from "@bitwarden/common/vault/models/request/cipher-bulk-delete.request"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { + CenterPositionStrategy, DIALOG_DATA, DialogConfig, DialogRef, @@ -48,7 +49,10 @@ export const openBulkDeleteDialog = ( ) => { return dialogService.open<BulkDeleteDialogResult, BulkDeleteDialogParams>( BulkDeleteDialogComponent, - config, + { + positionStrategy: new CenterPositionStrategy(), + ...config, + }, ); }; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 12a5432c4b8..6995549e845 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -26,7 +26,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService } from "@bitwarden/components"; import { OrganizationCounts } from "../models/view/counts.view"; import { ProjectListView } from "../models/view/project-list.view"; @@ -341,6 +341,7 @@ export class OverviewComponent implements OnInit, OnDestroy { data: { secrets: event, }, + positionStrategy: new CenterPositionStrategy(), }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index 7112a28010f..9cd570a734a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -21,7 +21,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService } from "@bitwarden/components"; import { ProjectView } from "../../models/view/project.view"; import { SecretListView } from "../../models/view/secret-list.view"; @@ -126,6 +126,7 @@ export class ProjectSecretsComponent implements OnInit { data: { secrets: event, }, + positionStrategy: new CenterPositionStrategy(), }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 6376b58423d..53325fe2f54 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -19,6 +19,7 @@ import { DialogService, BitValidators, ToastService, + CenterPositionStrategy, } from "@bitwarden/components"; import { SecretAccessPoliciesView } from "../../models/view/access-policies/secret-access-policies.view"; @@ -225,6 +226,7 @@ export class SecretDialogComponent implements OnInit, OnDestroy { data: { secrets: secretListView, }, + positionStrategy: new CenterPositionStrategy(), }, ); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index 46cccb1d95d..92b33a06d4f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -13,7 +13,12 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; +import { + CenterPositionStrategy, + DialogRef, + DialogService, + ToastService, +} from "@bitwarden/components"; import { openEntityEventsDialog } from "@bitwarden/web-vault/app/admin-console/organizations/manage/entity-events.component"; import { SecretListView } from "../models/view/secret-list.view"; @@ -180,6 +185,7 @@ export class SecretsComponent implements OnInit { data: { secrets: event, }, + positionStrategy: new CenterPositionStrategy(), }); } diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index b4da7769127..1e6483dcf92 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -6,7 +6,7 @@ import { combineLatestWith, Observable, startWith, switchMap } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; +import { CenterPositionStrategy, DialogService } from "@bitwarden/components"; import { SecretListView } from "../models/view/secret-list.view"; import { SecretService } from "../secrets/secret.service"; @@ -64,6 +64,7 @@ export class TrashComponent implements OnInit { secretIds: secretIds, organizationId: this.organizationId, }, + positionStrategy: new CenterPositionStrategy(), }); } @@ -73,6 +74,7 @@ export class TrashComponent implements OnInit { secretIds: secretIds, organizationId: this.organizationId, }, + positionStrategy: new CenterPositionStrategy(), }); } diff --git a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts index 48286a5d18c..ea9def03bd2 100644 --- a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts +++ b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.ts @@ -17,6 +17,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { ButtonModule, ButtonType, + CenterPositionStrategy, DialogModule, DialogRef, DialogService, @@ -114,6 +115,8 @@ export class PremiumUpgradeDialogComponent { * @returns A dialog reference object */ static open(dialogService: DialogService): DialogRef<PremiumUpgradeDialogComponent> { - return dialogService.open(PremiumUpgradeDialogComponent); + return dialogService.open(PremiumUpgradeDialogComponent, { + positionStrategy: new CenterPositionStrategy(), + }); } } diff --git a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts index 6ef36a32448..5e4d1cbdb49 100644 --- a/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts +++ b/libs/auth/src/angular/fingerprint-dialog/fingerprint-dialog.component.ts @@ -3,7 +3,13 @@ import { Component, Inject } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports -import { DIALOG_DATA, ButtonModule, DialogModule, DialogService } from "@bitwarden/components"; +import { + DIALOG_DATA, + ButtonModule, + DialogModule, + DialogService, + CenterPositionStrategy, +} from "@bitwarden/components"; export type FingerprintDialogData = { fingerprint: string[]; @@ -19,6 +25,9 @@ export class FingerprintDialogComponent { constructor(@Inject(DIALOG_DATA) protected data: FingerprintDialogData) {} static open(dialogService: DialogService, data: FingerprintDialogData) { - return dialogService.open(FingerprintDialogComponent, { data }); + return dialogService.open(FingerprintDialogComponent, { + data, + positionStrategy: new CenterPositionStrategy(), + }); } } diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 409bf0a5b55..1fc452418e1 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -5,7 +5,7 @@ import { DIALOG_DATA, DialogCloseOptions, } from "@angular/cdk/dialog"; -import { ComponentType, ScrollStrategy } from "@angular/cdk/overlay"; +import { ComponentType, GlobalPositionStrategy, ScrollStrategy } from "@angular/cdk/overlay"; import { ComponentPortal, Portal } from "@angular/cdk/portal"; import { Injectable, Injector, TemplateRef, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -17,6 +17,7 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authenticatio import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DrawerService } from "../drawer/drawer.service"; +import { isAtOrLargerThanBreakpoint } from "../utils/responsive-utils"; import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; import { SimpleDialogOptions } from "./simple-dialog/types"; @@ -63,6 +64,68 @@ export type DialogConfig<D = unknown, R = unknown> = Pick< "data" | "disableClose" | "ariaModal" | "positionStrategy" | "height" | "width" >; +/** + * A responsive position strategy that adjusts the dialog position based on the screen size. + */ +class ResponsivePositionStrategy extends GlobalPositionStrategy { + private abortController: AbortController | null = null; + + /** + * The previous breakpoint to avoid unnecessary updates. + * `null` means no previous breakpoint has been set. + */ + private prevBreakpoint: "small" | "large" | null = null; + + constructor() { + super(); + if (typeof window !== "undefined") { + this.abortController = new AbortController(); + this.updatePosition(); // Initial position update + window.addEventListener("resize", this.updatePosition.bind(this), { + signal: this.abortController.signal, + }); + } + } + + override dispose() { + this.abortController?.abort(); + this.abortController = null; + super.dispose(); + } + + updatePosition() { + const isSmallScreen = !isAtOrLargerThanBreakpoint("md"); + const currentBreakpoint = isSmallScreen ? "small" : "large"; + if (this.prevBreakpoint === currentBreakpoint) { + return; // No change in breakpoint, no need to update position + } + this.prevBreakpoint = currentBreakpoint; + if (isSmallScreen) { + this.bottom().centerHorizontally(); + } else { + this.centerVertically().centerHorizontally(); + } + this.apply(); + } +} + +/** + * Position strategy that centers dialogs regardless of screen size. + * Use this for simple dialogs and custom dialogs that should not use + * the responsive bottom-sheet behavior on mobile. + * + * @example + * dialogService.open(MyComponent, { + * positionStrategy: new CenterPositionStrategy() + * }); + */ +export class CenterPositionStrategy extends GlobalPositionStrategy { + constructor() { + super(); + this.centerHorizontally().centerVertically(); + } +} + class DrawerDialogRef<R = unknown, C = unknown> implements DialogRef<R, C> { readonly isDrawer = true; @@ -172,6 +235,7 @@ export class DialogService { const _config = { backdropClass: this.backDropClasses, scrollStrategy: this.defaultScrollStrategy, + positionStrategy: config?.positionStrategy ?? new ResponsivePositionStrategy(), injector, ...config, }; @@ -226,6 +290,7 @@ export class DialogService { return this.open<boolean, SimpleDialogOptions>(SimpleConfigurableDialogComponent, { data: simpleDialogOptions, disableClose: simpleDialogOptions.disableClose, + positionStrategy: new CenterPositionStrategy(), }); } diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 5774d83e349..83cfa21ed21 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -1,8 +1,10 @@ @let isDrawer = dialogRef?.isDrawer; <section class="tw-flex tw-w-full tw-flex-col tw-self-center tw-overflow-hidden tw-border tw-border-solid tw-border-secondary-100 tw-bg-background tw-text-main" - [ngClass]="[width, isDrawer ? 'tw-h-screen tw-border-t-0' : 'tw-rounded-xl tw-shadow-lg']" - @fadeIn + [ngClass]="[ + width, + isDrawer ? 'tw-h-screen tw-border-t-0' : 'tw-rounded-t-xl md:tw-rounded-xl tw-shadow-lg', + ]" cdkTrapFocus cdkTrapFocusAutoCapture > diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 71d594ef19e..954f03aabe2 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -3,13 +3,14 @@ import { CdkScrollable } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; import { Component, - HostBinding, inject, viewChild, input, booleanAttribute, ElementRef, DestroyRef, + computed, + signal, } from "@angular/core"; import { toObservable } from "@angular/core/rxjs-interop"; import { combineLatest, switchMap } from "rxjs"; @@ -21,7 +22,6 @@ import { SpinnerComponent } from "../../spinner"; import { TypographyDirective } from "../../typography/typography.directive"; import { hasScrollableContent$ } from "../../utils/"; import { hasScrolledFrom } from "../../utils/has-scrolled-from"; -import { fadeIn } from "../animations"; import { DialogRef } from "../dialog.service"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; @@ -31,9 +31,10 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai @Component({ selector: "bit-dialog", templateUrl: "./dialog.component.html", - animations: [fadeIn], host: { + "[class]": "classes()", "(keydown.esc)": "handleEsc($event)", + "(animationend)": "onAnimationEnd()", }, imports: [ CommonModule, @@ -87,22 +88,34 @@ export class DialogComponent { */ readonly disablePadding = input(false, { transform: booleanAttribute }); + /** + * Disable animations for the dialog. + */ + readonly disableAnimations = input(false, { transform: booleanAttribute }); + /** * Mark the dialog as loading which replaces the content with a spinner. */ readonly loading = input(false); - @HostBinding("class") get classes() { + private readonly animationCompleted = signal(false); + + protected readonly classes = computed(() => { // `tw-max-h-[90vh]` is needed to prevent dialogs from overlapping the desktop header - return ["tw-flex", "tw-flex-col", "tw-w-screen"] - .concat( - this.width, - this.dialogRef?.isDrawer - ? ["tw-min-h-screen", "md:tw-w-[23rem]"] - : ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"], - ) - .flat(); - } + const baseClasses = ["tw-flex", "tw-flex-col", "tw-w-screen"]; + const sizeClasses = this.dialogRef?.isDrawer + ? ["tw-min-h-screen", "md:tw-w-[23rem]"] + : ["md:tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"]; + + const animationClasses = + this.disableAnimations() || this.animationCompleted() || this.dialogRef?.isDrawer + ? [] + : this.dialogSize() === "small" + ? ["tw-animate-slide-down"] + : ["tw-animate-slide-up", "md:tw-animate-slide-down"]; + + return [...baseClasses, this.width, ...sizeClasses, ...animationClasses]; + }); handleEsc(event: Event) { if (!this.dialogRef?.disableClose) { @@ -124,4 +137,8 @@ export class DialogComponent { } } } + + onAnimationEnd() { + this.animationCompleted.set(true); + } } diff --git a/libs/components/src/dialog/dialog/dialog.stories.ts b/libs/components/src/dialog/dialog/dialog.stories.ts index d645d32764d..1f33ab7e877 100644 --- a/libs/components/src/dialog/dialog/dialog.stories.ts +++ b/libs/components/src/dialog/dialog/dialog.stories.ts @@ -57,6 +57,7 @@ export default { args: { loading: false, dialogSize: "small", + disableAnimations: true, }, argTypes: { _disablePadding: { @@ -71,6 +72,9 @@ export default { defaultValue: "default", }, }, + disableAnimations: { + control: { type: "boolean" }, + }, }, parameters: { design: { @@ -86,7 +90,7 @@ export const Default: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <bit-dialog [dialogSize]="dialogSize" [title]="title" [subtitle]="subtitle" [loading]="loading" [disablePadding]="disablePadding"> + <bit-dialog [dialogSize]="dialogSize" [title]="title" [subtitle]="subtitle" [loading]="loading" [disablePadding]="disablePadding" [disableAnimations]="disableAnimations"> <ng-container bitDialogTitle> <span bitBadge variant="success">Foobar</span> </ng-container> @@ -158,7 +162,7 @@ export const ScrollingContent: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <bit-dialog title="Scrolling Example" [background]="background" [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding"> + <bit-dialog title="Scrolling Example" [background]="background" [dialogSize]="dialogSize" [loading]="loading" [disablePadding]="disablePadding" [disableAnimations]="disableAnimations"> <span bitDialogContent> Dialog body text goes here.<br /> <ng-container *ngFor="let _ of [].constructor(100)"> @@ -175,6 +179,7 @@ export const ScrollingContent: Story = { }), args: { dialogSize: "small", + disableAnimations: true, }, }; @@ -182,7 +187,7 @@ export const TabContent: Story = { render: (args) => ({ props: args, template: /*html*/ ` - <bit-dialog title="Tab Content Example" [background]="background" [dialogSize]="dialogSize" [disablePadding]="disablePadding"> + <bit-dialog title="Tab Content Example" [background]="background" [dialogSize]="dialogSize" [disablePadding]="disablePadding" [disableAnimations]="disableAnimations"> <span bitDialogContent> <bit-tab-group> <bit-tab label="First Tab">First Tab Content</bit-tab> @@ -200,6 +205,7 @@ export const TabContent: Story = { args: { dialogSize: "large", disablePadding: true, + disableAnimations: true, }, parameters: { docs: { @@ -219,7 +225,7 @@ export const WithCards: Story = { }, template: /*html*/ ` <form [formGroup]="formObj"> - <bit-dialog [dialogSize]="dialogSize" [background]="background" [title]="title" [subtitle]="subtitle" [loading]="loading" [disablePadding]="disablePadding"> + <bit-dialog [dialogSize]="dialogSize" [background]="background" [title]="title" [subtitle]="subtitle" [loading]="loading" [disablePadding]="disablePadding" [disableAnimations]="disableAnimations"> <ng-container bitDialogContent> <bit-section> <bit-section-header> @@ -283,5 +289,6 @@ export const WithCards: Story = { title: "Default", subtitle: "Subtitle", background: "alt", + disableAnimations: true, }, }; diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index 5c94a959f25..b682b9f772a 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -9,7 +9,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { ButtonModule } from "../../button"; import { I18nMockService } from "../../utils/i18n-mock.service"; import { DialogModule } from "../dialog.module"; -import { DialogService } from "../dialog.service"; +import { CenterPositionStrategy, DialogService } from "../dialog.service"; interface Animal { animal: string; @@ -37,6 +37,7 @@ class StoryDialogComponent { data: { animal: "panda", }, + positionStrategy: new CenterPositionStrategy(), }); } @@ -46,6 +47,7 @@ class StoryDialogComponent { animal: "panda", }, disableClose: true, + positionStrategy: new CenterPositionStrategy(), }); } @@ -55,6 +57,7 @@ class StoryDialogComponent { animal: "panda", }, disableClose: true, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/libs/components/src/navigation/side-nav.service.ts b/libs/components/src/navigation/side-nav.service.ts index 979cba1e3de..ce44811c7e0 100644 --- a/libs/components/src/navigation/side-nav.service.ts +++ b/libs/components/src/navigation/side-nav.service.ts @@ -2,32 +2,30 @@ import { Injectable } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { BehaviorSubject, Observable, combineLatest, fromEvent, map, startWith } from "rxjs"; -type CollapsePreference = "open" | "closed" | null; +import { BREAKPOINTS, isAtOrLargerThanBreakpoint } from "../utils/responsive-utils"; -const SMALL_SCREEN_BREAKPOINT_PX = 768; +type CollapsePreference = "open" | "closed" | null; @Injectable({ providedIn: "root", }) export class SideNavService { - private _open$ = new BehaviorSubject<boolean>( - !window.matchMedia(`(max-width: ${SMALL_SCREEN_BREAKPOINT_PX}px)`).matches, - ); + private _open$ = new BehaviorSubject<boolean>(isAtOrLargerThanBreakpoint("md")); open$ = this._open$.asObservable(); - private isSmallScreen$ = media(`(max-width: ${SMALL_SCREEN_BREAKPOINT_PX}px)`); + private isLargeScreen$ = media(`(min-width: ${BREAKPOINTS.md}px)`); private _userCollapsePreference$ = new BehaviorSubject<CollapsePreference>(null); userCollapsePreference$ = this._userCollapsePreference$.asObservable(); - isOverlay$ = combineLatest([this.open$, this.isSmallScreen$]).pipe( - map(([open, isSmallScreen]) => open && isSmallScreen), + isOverlay$ = combineLatest([this.open$, this.isLargeScreen$]).pipe( + map(([open, isLargeScreen]) => open && !isLargeScreen), ); constructor() { - combineLatest([this.isSmallScreen$, this.userCollapsePreference$]) + combineLatest([this.isLargeScreen$, this.userCollapsePreference$]) .pipe(takeUntilDestroyed()) - .subscribe(([isSmallScreen, userCollapsePreference]) => { - if (isSmallScreen) { + .subscribe(([isLargeScreen, userCollapsePreference]) => { + if (!isLargeScreen) { this.setClose(); } else if (userCollapsePreference !== "closed") { // Auto-open when user hasn't set preference (null) or prefers open diff --git a/libs/components/src/utils/responsive-utils.ts b/libs/components/src/utils/responsive-utils.ts new file mode 100644 index 00000000000..a9c2499c275 --- /dev/null +++ b/libs/components/src/utils/responsive-utils.ts @@ -0,0 +1,27 @@ +/** + * Breakpoint definitions in pixels matching Tailwind CSS default breakpoints. + * These values must stay in sync with tailwind.config.base.js theme.extend configuration. + * + * @see {@link https://tailwindcss.com/docs/responsive-design} for tailwind default breakpoints + * @see {@link /libs/components/src/stories/responsive-design.mdx} for design system usage + */ +export const BREAKPOINTS = { + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + "2xl": 1536, +}; + +/** + * Checks if the current viewport is at or larger than the specified breakpoint. + * @param size The breakpoint to check. + * @returns True if the viewport is at or larger than the breakpoint, false otherwise. + */ +export const isAtOrLargerThanBreakpoint = (size: keyof typeof BREAKPOINTS): boolean => { + if (typeof window === "undefined" || !window.matchMedia) { + return false; + } + const query = `(min-width: ${BREAKPOINTS[size]}px)`; + return window.matchMedia(query).matches; +}; diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index ce399d860c1..e41cff16e48 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -167,6 +167,20 @@ module.exports = { container: { "@5xl": "1100px", }, + keyframes: { + slideUp: { + "0%": { opacity: "0", transform: "translateY(50px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + slideDown: { + "0%": { opacity: "0", transform: "translateY(-50px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + }, + animation: { + "slide-up": "slideUp 0.3s ease-out", + "slide-down": "slideDown 0.3s ease-out", + }, }, }, plugins: [ diff --git a/libs/vault/src/cipher-form/components/autofill-options/advanced-uri-option-dialog.component.ts b/libs/vault/src/cipher-form/components/autofill-options/advanced-uri-option-dialog.component.ts index f78c2c170f8..3580b1fada8 100644 --- a/libs/vault/src/cipher-form/components/autofill-options/advanced-uri-option-dialog.component.ts +++ b/libs/vault/src/cipher-form/components/autofill-options/advanced-uri-option-dialog.component.ts @@ -9,6 +9,7 @@ import { DialogService, DIALOG_DATA, DialogRef, + CenterPositionStrategy, } from "@bitwarden/components"; export type AdvancedUriOptionDialogParams = { @@ -55,6 +56,7 @@ export class AdvancedUriOptionDialogComponent { return dialogService.open<boolean>(AdvancedUriOptionDialogComponent, { data: params, disableClose: true, + positionStrategy: new CenterPositionStrategy(), }); } } diff --git a/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts b/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts index 628de79b3da..6b1a0e0d8aa 100644 --- a/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts +++ b/libs/vault/src/components/decryption-failure-dialog/decryption-failure-dialog.component.ts @@ -13,6 +13,7 @@ import { DialogModule, DialogService, TypographyModule, + CenterPositionStrategy, } from "@bitwarden/components"; export type DecryptionFailureDialogParams = { @@ -56,6 +57,9 @@ export class DecryptionFailureDialogComponent { } static open(dialogService: DialogService, params: DecryptionFailureDialogParams) { - return dialogService.open(DecryptionFailureDialogComponent, { data: params }); + return dialogService.open(DecryptionFailureDialogComponent, { + data: params, + positionStrategy: new CenterPositionStrategy(), + }); } } From ed2d8b9549e5ab1ccdbd67a24b8dbcaecf7e67a0 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu <acoroiu@bitwarden.com> Date: Fri, 14 Nov 2025 08:51:38 +0100 Subject: [PATCH 130/249] [PM-18046] Implement session storage (#17346) * feat: add support for IPC client managed session storage * feat: update SDK * fix: using undecorated service in jslib module directly * feat: add test case for web * chore: document why we use any type * fix: `ipc` too short * typo: omg * Revert "typo: omg" This reverts commit 559b05eb5ab8522b9c5455fc76e8d39afcf8c3d6. * Revert "fix: `ipc` too short" This reverts commit 35fc99e10b60daff993115024fd703a1f0960d12. * fix: use camelCase --- .../browser/src/background/main.background.ts | 9 +++- .../platform/ipc/ipc-background.service.ts | 6 ++- .../src/app/platform/ipc/web-ipc.service.ts | 12 ++++- .../src/services/jslib-services.module.ts | 6 +++ libs/common/src/platform/ipc/index.ts | 1 + .../ipc/ipc-session-repository.spec.ts | 49 ++++++++++++++++++ .../platform/ipc/ipc-session-repository.ts | 51 +++++++++++++++++++ libs/state/src/core/state-definitions.ts | 1 + package-lock.json | 16 +++--- package.json | 4 +- 10 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 libs/common/src/platform/ipc/ipc-session-repository.spec.ts create mode 100644 libs/common/src/platform/ipc/ipc-session-repository.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 97bfe804411..cff783942fe 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -131,7 +131,7 @@ import { } from "@bitwarden/common/platform/abstractions/storage.service"; import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service"; import { ActionsService } from "@bitwarden/common/platform/actions/actions-service"; -import { IpcService } from "@bitwarden/common/platform/ipc"; +import { IpcService, IpcSessionRepository } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency creation import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -1476,7 +1476,12 @@ export default class MainBackground { ); this.ipcContentScriptManagerService = new IpcContentScriptManagerService(this.configService); - this.ipcService = new IpcBackgroundService(this.platformUtilsService, this.logService); + const ipcSessionRepository = new IpcSessionRepository(this.stateProvider); + this.ipcService = new IpcBackgroundService( + this.platformUtilsService, + this.logService, + ipcSessionRepository, + ); this.endUserNotificationService = new DefaultEndUserNotificationService( this.stateProvider, diff --git a/apps/browser/src/platform/ipc/ipc-background.service.ts b/apps/browser/src/platform/ipc/ipc-background.service.ts index 911ca931c70..9fc2ca24b6a 100644 --- a/apps/browser/src/platform/ipc/ipc-background.service.ts +++ b/apps/browser/src/platform/ipc/ipc-background.service.ts @@ -8,6 +8,7 @@ import { OutgoingMessage, ipcRegisterDiscoverHandler, IpcClient, + IpcSessionRepository, } from "@bitwarden/sdk-internal"; import { BrowserApi } from "../browser/browser-api"; @@ -18,6 +19,7 @@ export class IpcBackgroundService extends IpcService { constructor( private platformUtilsService: PlatformUtilsService, private logService: LogService, + private sessionRepository: IpcSessionRepository, ) { super(); } @@ -70,7 +72,9 @@ export class IpcBackgroundService extends IpcService { ); }); - await super.initWithClient(new IpcClient(this.communicationBackend)); + await super.initWithClient( + IpcClient.newWithClientManagedSessions(this.communicationBackend, this.sessionRepository), + ); if (this.platformUtilsService.isDev()) { await ipcRegisterDiscoverHandler(this.client, { diff --git a/apps/web/src/app/platform/ipc/web-ipc.service.ts b/apps/web/src/app/platform/ipc/web-ipc.service.ts index 590c1f36cc4..c6614759b44 100644 --- a/apps/web/src/app/platform/ipc/web-ipc.service.ts +++ b/apps/web/src/app/platform/ipc/web-ipc.service.ts @@ -3,7 +3,12 @@ import { inject } from "@angular/core"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; -import { IpcMessage, IpcService, isIpcMessage } from "@bitwarden/common/platform/ipc"; +import { + IpcMessage, + IpcService, + isIpcMessage, + IpcSessionRepository, +} from "@bitwarden/common/platform/ipc"; import { IncomingMessage, IpcClient, @@ -15,6 +20,7 @@ import { export class WebIpcService extends IpcService { private logService = inject(LogService); private platformUtilsService = inject(PlatformUtilsService); + private sessionRepository = inject(IpcSessionRepository); private communicationBackend?: IpcCommunicationBackend; override async init() { @@ -70,7 +76,9 @@ export class WebIpcService extends IpcService { ); }); - await super.initWithClient(new IpcClient(this.communicationBackend)); + await super.initWithClient( + IpcClient.newWithClientManagedSessions(this.communicationBackend, this.sessionRepository), + ); if (this.platformUtilsService.isDev()) { await ipcRegisterDiscoverHandler(this.client, { diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 18c21024a6a..9dbc6679963 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -228,6 +228,7 @@ import { SystemService } from "@bitwarden/common/platform/abstractions/system.se import { ValidationService as ValidationServiceAbstraction } from "@bitwarden/common/platform/abstractions/validation.service"; import { ActionsService } from "@bitwarden/common/platform/actions"; import { UnsupportedActionsService } from "@bitwarden/common/platform/actions/unsupported-actions.service"; +import { IpcSessionRepository } from "@bitwarden/common/platform/ipc"; import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging"; // eslint-disable-next-line no-restricted-imports -- Used for dependency injection import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal"; @@ -1750,6 +1751,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultNewDeviceVerificationComponentService, deps: [], }), + safeProvider({ + provide: IpcSessionRepository, + useClass: IpcSessionRepository, + deps: [StateProvider], + }), safeProvider({ provide: PremiumInterestStateService, useClass: NoopPremiumInterestStateService, diff --git a/libs/common/src/platform/ipc/index.ts b/libs/common/src/platform/ipc/index.ts index f1acccdddbf..3fa6aeb627d 100644 --- a/libs/common/src/platform/ipc/index.ts +++ b/libs/common/src/platform/ipc/index.ts @@ -1,2 +1,3 @@ export * from "./ipc-message"; export * from "./ipc.service"; +export * from "./ipc-session-repository"; diff --git a/libs/common/src/platform/ipc/ipc-session-repository.spec.ts b/libs/common/src/platform/ipc/ipc-session-repository.spec.ts new file mode 100644 index 00000000000..62437455b08 --- /dev/null +++ b/libs/common/src/platform/ipc/ipc-session-repository.spec.ts @@ -0,0 +1,49 @@ +import { FakeActiveUserAccessor, FakeStateProvider } from "../../../spec"; +import { UserId } from "../../types/guid"; + +import { IpcSessionRepository } from "./ipc-session-repository"; + +describe("IpcSessionRepository", () => { + const userId = "user-id" as UserId; + let stateProvider!: FakeStateProvider; + let repository!: IpcSessionRepository; + + beforeEach(() => { + stateProvider = new FakeStateProvider(new FakeActiveUserAccessor(userId)); + repository = new IpcSessionRepository(stateProvider); + }); + + it("returns undefined when empty", async () => { + const result = await repository.get("BrowserBackground"); + + expect(result).toBeUndefined(); + }); + + it("saves and retrieves a session", async () => { + const session = { some: "data" }; + await repository.save("BrowserBackground", session); + + const result = await repository.get("BrowserBackground"); + + expect(result).toEqual(session); + }); + + it("saves and retrieves a web session", async () => { + const session = { some: "data" }; + await repository.save({ Web: { id: 9001 } }, session); + + const result = await repository.get({ Web: { id: 9001 } }); + + expect(result).toEqual(session); + }); + + it("removes a session", async () => { + const session = { some: "data" }; + await repository.save("BrowserBackground", session); + + await repository.remove("BrowserBackground"); + const result = await repository.get("BrowserBackground"); + + expect(result).toBeUndefined(); + }); +}); diff --git a/libs/common/src/platform/ipc/ipc-session-repository.ts b/libs/common/src/platform/ipc/ipc-session-repository.ts new file mode 100644 index 00000000000..c9f5fe4a355 --- /dev/null +++ b/libs/common/src/platform/ipc/ipc-session-repository.ts @@ -0,0 +1,51 @@ +import { firstValueFrom, map } from "rxjs"; + +import { Endpoint, IpcSessionRepository as SdkIpcSessionRepository } from "@bitwarden/sdk-internal"; + +import { GlobalState, IPC_MEMORY, KeyDefinition, StateProvider } from "../state"; + +const IPC_SESSIONS = KeyDefinition.record<object, string>(IPC_MEMORY, "ipcSessions", { + deserializer: (value: object) => value, +}); + +/** + * Implementation of SDK-defined repository interface/trait. Do not use directly. + * All error handling is done by the caller (the SDK). + * For more information see IPC docs. + * + * Interface uses `any` type as defined by the SDK until we get a concrete session type. + */ +export class IpcSessionRepository implements SdkIpcSessionRepository { + private states: GlobalState<Record<string, any>>; + + constructor(private stateProvider: StateProvider) { + this.states = this.stateProvider.getGlobal(IPC_SESSIONS); + } + + get(endpoint: Endpoint): Promise<any | undefined> { + return firstValueFrom(this.states.state$.pipe(map((s) => s?.[endpointToString(endpoint)]))); + } + + async save(endpoint: Endpoint, session: any): Promise<void> { + await this.states.update((s) => ({ + ...s, + [endpointToString(endpoint)]: session, + })); + } + + async remove(endpoint: Endpoint): Promise<void> { + await this.states.update((s) => { + const newState = { ...s }; + delete newState[endpointToString(endpoint)]; + return newState; + }); + } +} + +function endpointToString(endpoint: Endpoint): string { + if (typeof endpoint === "object" && "Web" in endpoint) { + return `Web(${endpoint.Web.id})`; + } + + return endpoint; +} diff --git a/libs/state/src/core/state-definitions.ts b/libs/state/src/core/state-definitions.ts index 7b1d75b2985..9d404f14dd7 100644 --- a/libs/state/src/core/state-definitions.ts +++ b/libs/state/src/core/state-definitions.ts @@ -127,6 +127,7 @@ export const CRYPTO_MEMORY = new StateDefinition("crypto", "memory"); export const DESKTOP_SETTINGS_DISK = new StateDefinition("desktopSettings", "disk"); export const ENVIRONMENT_DISK = new StateDefinition("environment", "disk"); export const ENVIRONMENT_MEMORY = new StateDefinition("environment", "memory"); +export const IPC_MEMORY = new StateDefinition("interProcessCommunication", "memory"); export const POPUP_VIEW_MEMORY = new StateDefinition("popupView", "memory", { browser: "memory-large-object", }); diff --git a/package-lock.json b/package-lock.json index d2a034e05a9..46b70931f65 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.374", - "@bitwarden/sdk-internal": "0.2.0-main.374", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", + "@bitwarden/sdk-internal": "0.2.0-main.375", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4607,9 +4607,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.374", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.374.tgz", - "integrity": "sha512-OYNjEv9Z9Y1vCDWtlp7m49+Fu0WxCyJt+DDupF8T73JqWIl2SdY3ugLtLnCUnqause5VY7OAfa4eOxwn2ONKZg==", + "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==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4712,9 +4712,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.374", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.374.tgz", - "integrity": "sha512-P9td//6M22Eg8YcVOVtcvkD9wfdbnwNe7lZ1HGn74o3CTgDtNq0mE5x00rDeNZq0ctBaUDaqw6XS0jC/tehcag==", + "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==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 3eb6b1619cc..f9757aa1e68 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.374", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.374", + "@bitwarden/sdk-internal": "0.2.0-main.375", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.375", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From dfc640d365d7e83dfbfd4a770878bed9152472fb Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:28:37 +0100 Subject: [PATCH 131/249] Enable more angular-eslint rules (#17383) --- eslint.config.mjs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 6c362a4dc43..bfa8b6ec079 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -62,16 +62,15 @@ export default tseslint.config( // TODO: Enable these. "@angular-eslint/component-class-suffix": 0, - "@angular-eslint/contextual-lifecycle": 0, + "@angular-eslint/contextual-lifecycle": "error", "@angular-eslint/directive-class-suffix": 0, "@angular-eslint/no-empty-lifecycle-method": 0, - "@angular-eslint/no-host-metadata-property": 0, "@angular-eslint/no-input-rename": 0, - "@angular-eslint/no-inputs-metadata-property": 0, + "@angular-eslint/no-inputs-metadata-property": "error", "@angular-eslint/no-output-native": 0, "@angular-eslint/no-output-on-prefix": 0, - "@angular-eslint/no-output-rename": 0, - "@angular-eslint/no-outputs-metadata-property": 0, + "@angular-eslint/no-output-rename": "error", + "@angular-eslint/no-outputs-metadata-property": "error", "@angular-eslint/prefer-on-push-component-change-detection": "error", "@angular-eslint/prefer-output-emitter-ref": "error", "@angular-eslint/prefer-signals": "error", From 4fd65965e85098b97aa425856f40ca665aa32cf1 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:39:05 +0100 Subject: [PATCH 132/249] Autosync the updated translations (#17379) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 18 ++ apps/browser/src/_locales/az/messages.json | 70 +++-- apps/browser/src/_locales/be/messages.json | 18 ++ apps/browser/src/_locales/bg/messages.json | 18 ++ apps/browser/src/_locales/bn/messages.json | 18 ++ apps/browser/src/_locales/bs/messages.json | 18 ++ apps/browser/src/_locales/ca/messages.json | 18 ++ apps/browser/src/_locales/cs/messages.json | 18 ++ apps/browser/src/_locales/cy/messages.json | 18 ++ apps/browser/src/_locales/da/messages.json | 18 ++ apps/browser/src/_locales/de/messages.json | 24 +- apps/browser/src/_locales/el/messages.json | 18 ++ apps/browser/src/_locales/en_GB/messages.json | 18 ++ apps/browser/src/_locales/en_IN/messages.json | 18 ++ apps/browser/src/_locales/es/messages.json | 18 ++ apps/browser/src/_locales/et/messages.json | 18 ++ apps/browser/src/_locales/eu/messages.json | 18 ++ apps/browser/src/_locales/fa/messages.json | 18 ++ apps/browser/src/_locales/fi/messages.json | 18 ++ apps/browser/src/_locales/fil/messages.json | 18 ++ apps/browser/src/_locales/fr/messages.json | 20 +- apps/browser/src/_locales/gl/messages.json | 18 ++ apps/browser/src/_locales/he/messages.json | 18 ++ apps/browser/src/_locales/hi/messages.json | 18 ++ apps/browser/src/_locales/hr/messages.json | 18 ++ apps/browser/src/_locales/hu/messages.json | 20 +- apps/browser/src/_locales/id/messages.json | 18 ++ apps/browser/src/_locales/it/messages.json | 18 ++ apps/browser/src/_locales/ja/messages.json | 244 ++++++++++-------- apps/browser/src/_locales/ka/messages.json | 18 ++ apps/browser/src/_locales/km/messages.json | 18 ++ apps/browser/src/_locales/kn/messages.json | 18 ++ apps/browser/src/_locales/ko/messages.json | 18 ++ apps/browser/src/_locales/lt/messages.json | 18 ++ apps/browser/src/_locales/lv/messages.json | 18 ++ apps/browser/src/_locales/ml/messages.json | 18 ++ apps/browser/src/_locales/mr/messages.json | 18 ++ apps/browser/src/_locales/my/messages.json | 18 ++ apps/browser/src/_locales/nb/messages.json | 18 ++ apps/browser/src/_locales/ne/messages.json | 18 ++ apps/browser/src/_locales/nl/messages.json | 18 ++ apps/browser/src/_locales/nn/messages.json | 18 ++ apps/browser/src/_locales/or/messages.json | 18 ++ apps/browser/src/_locales/pl/messages.json | 18 ++ apps/browser/src/_locales/pt_BR/messages.json | 18 ++ apps/browser/src/_locales/pt_PT/messages.json | 18 ++ apps/browser/src/_locales/ro/messages.json | 18 ++ apps/browser/src/_locales/ru/messages.json | 18 ++ apps/browser/src/_locales/si/messages.json | 18 ++ apps/browser/src/_locales/sk/messages.json | 18 ++ apps/browser/src/_locales/sl/messages.json | 18 ++ apps/browser/src/_locales/sr/messages.json | 76 +++--- apps/browser/src/_locales/sv/messages.json | 18 ++ apps/browser/src/_locales/ta/messages.json | 18 ++ apps/browser/src/_locales/te/messages.json | 18 ++ apps/browser/src/_locales/th/messages.json | 18 ++ apps/browser/src/_locales/tr/messages.json | 18 ++ apps/browser/src/_locales/uk/messages.json | 18 ++ apps/browser/src/_locales/vi/messages.json | 18 ++ apps/browser/src/_locales/zh_CN/messages.json | 20 +- apps/browser/src/_locales/zh_TW/messages.json | 20 +- 61 files changed, 1273 insertions(+), 175 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 053fb3b101f..79d54193b59 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "عند قفل النظام" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "عند إعادة تشغيل المتصفح" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index 68ea40b6808..b67d5ace0d4 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -592,7 +592,10 @@ "message": "Bax" }, "viewAll": { - "message": "View all" + "message": "Hamısına bax" + }, + "viewLess": { + "message": "View less" }, "viewLogin": { "message": "Girişə bax" @@ -625,7 +628,7 @@ "message": "Element sevimlilərə əlavə edildi" }, "itemRemovedFromFavorites": { - "message": "Element sevimlilərdən çıxarıldı" + "message": "Element sevimlilərdən xaric edildi" }, "notes": { "message": "Notlar" @@ -685,7 +688,7 @@ "message": "Ayarlarda bir kilid açma üsulu qurun" }, "sessionTimeoutHeader": { - "message": "Seans vaxt bitməsi" + "message": "Sessiya vaxt bitməsi" }, "vaultTimeoutHeader": { "message": "Seyf vaxtının bitməsi" @@ -796,6 +799,12 @@ "onLocked": { "message": "Sistem kilidlənəndə" }, + "onIdle": { + "message": "Sistem boşda olduqda" + }, + "onSleep": { + "message": "Sistem yuxu rejimində olduqda" + }, "onRestart": { "message": "Brauzer yenidən başladılanda" }, @@ -916,7 +925,7 @@ "message": "Hesabınızdan çıxış etmisiniz." }, "loginExpired": { - "message": "Giriş seansınızın müddəti bitdi." + "message": "Giriş sessiyanızın müddəti bitdi." }, "logIn": { "message": "Giriş et" @@ -1035,10 +1044,10 @@ "message": "Element saxlanıldı" }, "savedWebsite": { - "message": "Saved website" + "message": "Saxlanılan veb sayt" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Saxlanılan veb sayt ( $COUNT$ )", "placeholders": { "count": { "content": "$1", @@ -1239,7 +1248,7 @@ "description": "Detailed error message shown when saving login details fails." }, "changePasswordWarning": { - "message": "Parolunuzu dəyişdirdikdən sonra yeni parolunuzla giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saat ərzində çıxış sonlandırılacaq." + "message": "Parolunuzu dəyişdirdikdən sonra yeni parolunuzla giriş etməli olacaqsınız. Digər cihazlardakı aktiv sessiyalar bir saat ərzində çıxış sonlandırılacaq." }, "accountRecoveryUpdateMasterPasswordSubtitle": { "message": "Hesabın geri qaytarılması prosesini tamamlamaq üçün ana parolunuzu dəyişdirin." @@ -1527,7 +1536,7 @@ "message": "Kimlik doğrulama vaxtı bitdi" }, "authenticationSessionTimedOut": { - "message": "Kimlik doğrulama seansının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." + "message": "Kimlik doğrulama sessiyasının vaxtı bitdi. Lütfən giriş prosesini yenidən başladın." }, "verificationCodeEmailSent": { "message": "Doğrulama poçtu $EMAIL$ ünvanına göndərildi.", @@ -1692,28 +1701,28 @@ "message": "Avto-doldurmanı söndür" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Avto-doldurmanı təsdiqlə" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Bu sayt, saxlanılmış giriş məlumatlarınızla uyuşmur. Giriş məlumatlarınızı doldurmazdan əvvəl, güvənli sayt olduğuna əmin olun." }, "showInlineMenuLabel": { "message": "Avto-doldurma təkliflərini form xanalarında göstər" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Bitwarden verilərinizi fişinqdən necə qoruyur?" }, "currentWebsite": { - "message": "Current website" + "message": "Hazırkı veb sayt" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Avto-doldur və bu veb saytı əlavə et" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Əlavə etmədən avto-doldur" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Avto-doldurulmasın" }, "showInlineMenuIdentitiesLabel": { "message": "Kimlikləri təklif kimi göstər" @@ -2149,7 +2158,7 @@ } }, "passwordSafe": { - "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz." + "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Əmniyyətlə istifadə edə bilərsiniz." }, "baseDomain": { "message": "Baza domeni", @@ -2219,7 +2228,7 @@ "message": "Təzəlikcə heç nə yaratmamısınız" }, "remove": { - "message": "Çıxart" + "message": "Xaric et" }, "default": { "message": "İlkin" @@ -3058,10 +3067,10 @@ "message": "Ana parolu güncəllə" }, "updateMasterPasswordWarning": { - "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir inzibatçı tərəfindən dəyişdirildi. Seyfə erişmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təzəlikcə təşkilatınızdakı bir inzibatçı tərəfindən dəyişdirildi. Seyfə erişmək üçün onu indi güncəlləməlisiniz. Davam etsəniz, hazırkı sessiyadan çıxış edəcəksiniz və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv sessiyalar bir saata qədər aktiv qalmağa davam edə bilər." }, "updateWeakMasterPasswordWarning": { - "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə erişmək üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı seansdan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv seanslar bir saata qədər aktiv qalmağa davam edə bilər." + "message": "Ana parolunuz təşkilatınızdakı siyasətlərdən birinə və ya bir neçəsinə uyğun gəlmir. Seyfə erişmək üçün ana parolunuzu indi güncəlləməlisiniz. Davam etsəniz, hazırkı sessiyadan çıxış etmiş və təkrar giriş etməli olacaqsınız. Digər cihazlardakı aktiv sessiyalar bir saata qədər aktiv qalmağa davam edə bilər." }, "tdeDisabledMasterPasswordRequired": { "message": "Təşkilatınız, güvənli cihaz şifrələməsini sıradan çıxartdı. Seyfinizə erişmək üçün lütfən ana parol təyin edin." @@ -3217,7 +3226,7 @@ "message": "Simvol sayını dəyişdir" }, "sessionTimeout": { - "message": "Seansınızın vaxtı bitdi. Lütfən geri qayıdıb yenidən giriş etməyə cəhd edin." + "message": "Sessiyanızın vaxtı bitdi. Lütfən geri qayıdıb yenidən giriş etməyə cəhd edin." }, "exportingPersonalVaultTitle": { "message": "Fərdi seyfin xaricə köçürülməsi" @@ -3277,7 +3286,7 @@ "message": "Şifrə açma xətası" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Avto-doldurma verilərini alma xətası" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden, aşağıda sadalanan seyf element(lər)inin şifrəsini aça bilmədi." @@ -3723,7 +3732,7 @@ "message": "Cihazları idarə et" }, "currentSession": { - "message": "Hazırkı seans" + "message": "Hazırkı sessiya" }, "mobile": { "message": "Mobil", @@ -3920,7 +3929,7 @@ "message": "İstifadəçiyə güvən" }, "sendsTitleNoItems": { - "message": "Send, həssas məlumatlar təhlükəsizdir", + "message": "Send ilə həssas məlumatlar əmniyyətdədir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { @@ -4051,13 +4060,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Avto-doldurula bilmir" }, "cannotAutofillExactMatch": { "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." }, "okay": { - "message": "Okay" + "message": "Oldu" }, "toggleSideNavigation": { "message": "Yan naviqasiyanı aç/bağla" @@ -5709,7 +5718,7 @@ "message": "Kimliklərinizlə, uzun qeydiyyat və ya əlaqə xanalarını daha tez avtomatik doldurun." }, "newNoteNudgeTitle": { - "message": "Həssas verilərinizi güvənli şəkildə saxlayın" + "message": "Həssas verilərinizi əmniyyətdə saxlayın" }, "newNoteNudgeBody": { "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas veriləri təhlükəsiz saxlayın." @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" }, + "loadingVault": { + "message": "Seyf yüklənir" + }, + "vaultLoaded": { + "message": "Seyf yükləndi" + }, "settingDisabledByPolicy": { "message": "Bu ayar, təşkilatınızın siyasəti tərəfindən sıradan çıxarılıb.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kart nömrəsi" + }, + "sessionTimeoutSettingsAction": { + "message": "Vaxt bitmə əməliyyatı" } } diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index b0735109b41..450fb6e3df5 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Пры блакіраванні сістэмы" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Пры перазапуску браўзера" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 30386fe625e..0a71e453c21 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Показване на всички" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Преглед на елемента за вписване" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "При заключване на системата" }, + "onIdle": { + "message": "При бездействие на системата" + }, + "onSleep": { + "message": "При заспиване на системата" + }, "onRestart": { "message": "При повторно пускане на браузъра" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Надградете до Платения план" }, + "loadingVault": { + "message": "Зареждане на трезора" + }, + "vaultLoaded": { + "message": "Трезорът е зареден" + }, "settingDisabledByPolicy": { "message": "Тази настройка е изключена съгласно политиката на организацията Ви.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Номер на картата" + }, + "sessionTimeoutSettingsAction": { + "message": "Действие при изтичането на времето за достъп" } } diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index d68d19b0a05..f43e3fdad29 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "সিস্টেম লকে" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "ব্রাউজার পুনঃসূচনাই" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 74f47fac7df..4fbcccd9aae 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 824f37f069e..15a309fa8fd 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "En bloquejar el sistema" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "En reiniciar el navegador" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 46f5f414a1a..f9f572d87d8 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Zobrazit vše" }, + "viewLess": { + "message": "Zobrazit méně" + }, "viewLogin": { "message": "Zobrazit přihlašovací údaje" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Při uzamknutí systému" }, + "onIdle": { + "message": "Při nečinnosti systému" + }, + "onSleep": { + "message": "Při přechodu do režimu spánku" + }, "onRestart": { "message": "Při restartu prohlížeče" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Aktualizovat na Premium" }, + "loadingVault": { + "message": "Načítání trezoru" + }, + "vaultLoaded": { + "message": "Trezor byl načten" + }, "settingDisabledByPolicy": { "message": "Toto nastavení je zakázáno zásadami Vaší organizace.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Číslo karty" + }, + "sessionTimeoutSettingsAction": { + "message": "Akce vypršení časového limitu" } } diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 07c5a68e3ec..33c68b338a0 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "wrth ailgychwyn y porwr" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 93b311e158b..a5999945692 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Når systemet låses" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Ved genstart af browseren" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index d88c396bb80..b72cc2decb4 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -29,7 +29,7 @@ "message": "Mit Passkey anmelden" }, "useSingleSignOn": { - "message": "Single Sign-on verwenden" + "message": "Single Sign-On verwenden" }, "yourOrganizationRequiresSingleSignOn": { "message": "Deine Organisation erfordert Single Sign-On." @@ -44,7 +44,7 @@ "message": "Schließe die Erstellung deines Kontos ab, indem du ein Passwort festlegst" }, "enterpriseSingleSignOn": { - "message": "Enterprise Single-Sign-On" + "message": "Enterprise Single Sign-On" }, "cancel": { "message": "Abbrechen" @@ -594,6 +594,9 @@ "viewAll": { "message": "Alles anzeigen" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Zugangsdaten anzeigen" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Wenn System gesperrt" }, + "onIdle": { + "message": "Bei Systeminaktivität" + }, + "onSleep": { + "message": "Im Standby" + }, "onRestart": { "message": "Bei Browser-Neustart" }, @@ -5789,7 +5798,7 @@ "message": "Notfallzugriff" }, "breachMonitoring": { - "message": "Datenpannen-Überwachung" + "message": "Datendiebstahl-Überwachung" }, "andMoreFeatures": { "message": "Und mehr!" @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade auf Premium" }, + "loadingVault": { + "message": "Tresor wird geladen" + }, + "vaultLoaded": { + "message": "Tresor geladen" + }, "settingDisabledByPolicy": { "message": "Diese Einstellung ist durch die Richtlinien deiner Organisation deaktiviert.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kartennummer" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout-Aktion" } } diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index 7fb60530511..e0de7e5e9e0 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Προβολή σύνδεσης" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Κατά το Κλείδωμα Συστήματος" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Κατά την Επανεκκίνηση του Browser" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 8ab541c569e..a9c57e157e6 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 68bf5497e37..cd8c91f8437 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organisation's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 060da79a4ff..470cf2ab35a 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Al bloquear el sistema" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Al reiniciar el navegador" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index acb440b2aa6..9220d61e466 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Arvutist väljalogimisel" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Brauseri taaskäivitamisel" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index 016381e17f8..c360bed28e0 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Sistema blokeatzean" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Nabigatzailea berrabiaraztean" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 4f8529b2710..774a02f50d3 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "هنگام قفل سیستم" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "هنگام راه‌اندازی مجدد" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index e3a5b44ea91..8766632a91e 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Näytä kaikki" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Kun järjestelmä lukitaan" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Kun selain käynnistetään uudelleen" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 2b58095d950..6c7154a1ba5 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Sa pag-lock ng sistema" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Sa pag-restart ng browser" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index afb58afcc25..0e701750c5b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Afficher l'Identifiant" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Au verrouillage" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Au redémarrage du navigateur" }, @@ -3283,7 +3292,7 @@ "message": "Bitwarden n’a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." }, "contactCSToAvoidDataLossPart1": { - "message": "Contacter le service clientèle", + "message": "Contacter succès client", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "Ce paramètre est désactivé par la politique de sécurité de votre organisation.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Numéro de carte" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 6d0410f112c..c61325ef8de 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Ó bloquear o sistema" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Ó reiniciar o navegador" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index cc78e1a154a..81ac1e176f0 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "הצג הכל" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "הצג כניסה" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "בנעילת המערכת" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "בהפעלת הדפדפן מחדש" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "הגדרה זו מושבתת על ידי מדיניות של הארגון שלך.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "מספר כרטיס" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index c27fa6f7eb7..ff24818f821 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On Locked" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On Restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 9d7539a9bd5..052fae33683 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Vidi sve" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Prikaži prijavu" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Pri zaključavanju sustava" }, + "onIdle": { + "message": "U stanju besposlenosti" + }, + "onSleep": { + "message": "U stanju mirovanja sustava" + }, "onRestart": { "message": "Pri pokretanju preglednika" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": " Nadogradi na Premium" }, + "loadingVault": { + "message": "Učitavanje trezora" + }, + "vaultLoaded": { + "message": "Trezor učitan" + }, "settingDisabledByPolicy": { "message": "Ova je postavka onemogućena pravilima tvoje organizacije.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Broj kartice" + }, + "sessionTimeoutSettingsAction": { + "message": "Radnja kod isteka" } } diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index 9f10494258a..fb94c4f4665 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Összes megtekintése" }, + "viewLess": { + "message": "kevesebb megjelenítése" + }, "viewLogin": { "message": "Bejelentkezés megtekintése" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Rendszerzároláskor" }, + "onIdle": { + "message": "Rendszer üresjárat esetén" + }, + "onSleep": { + "message": "Rendszer alvó mód esetén" + }, "onRestart": { "message": "Böngésző újraindításkor" }, @@ -4975,7 +4984,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Alapértelmezett ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Áttérés Prémium csomagra" }, + "loadingVault": { + "message": "Széf betöltése" + }, + "vaultLoaded": { + "message": "A széf betöltésre került." + }, "settingDisabledByPolicy": { "message": "Ezt a beállítást a szervezet házirendje letiltotta.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kártya szám" + }, + "sessionTimeoutSettingsAction": { + "message": "Időkifutási művelet" } } diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 26a2b8dc6bd..1cb79804923 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Saat Komputer Terkunci" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Saat Peramban Dimulai Ulang" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index 60c97d7157a..dc69cb13cb3 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Visualizza login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Al blocco del computer" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Al riavvio del browser" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "Questa impostazione è disabilitata dalle restrizioni della tua organizzazione.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 7c4420508a2..8b3b0e2cc6d 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -3,7 +3,7 @@ "message": "Bitwarden" }, "appLogoLabel": { - "message": "Bitwarden logo" + "message": "Bitwarden ロゴ" }, "extName": { "message": "Bitwarden パスワードマネージャー", @@ -32,7 +32,7 @@ "message": "シングルサインオンを使用する" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "あなたの組織ではシングルサインオン (SSO) を使用する必要があります。" }, "welcomeBack": { "message": "ようこそ" @@ -554,15 +554,15 @@ "message": "Reset search" }, "archiveNoun": { - "message": "Archive", + "message": "アーカイブ", "description": "Noun" }, "archiveVerb": { - "message": "Archive", + "message": "アーカイブ", "description": "Verb" }, "unArchive": { - "message": "Unarchive" + "message": "アーカイブ解除" }, "itemsInArchive": { "message": "Items in archive" @@ -594,8 +594,11 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { - "message": "View login" + "message": "ログイン情報を表示" }, "noItemsInList": { "message": "表示するアイテムがありません" @@ -796,6 +799,12 @@ "onLocked": { "message": "ロック時" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "ブラウザ再起動時" }, @@ -1035,10 +1044,10 @@ "message": "編集されたアイテム" }, "savedWebsite": { - "message": "Saved website" + "message": "保存されたウェブサイト" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "保存されたウェブサイト ($COUNT$ 件)", "placeholders": { "count": { "content": "$1", @@ -1145,10 +1154,10 @@ "description": "Tooltip and Aria label for edit button on cipher item" }, "newNotification": { - "message": "New notification" + "message": "新しい通知" }, "labelWithNotification": { - "message": "$LABEL$: New notification", + "message": "$LABEL$: 新しい通知", "description": "Label for the notification with a new login suggestion.", "placeholders": { "label": { @@ -1190,11 +1199,11 @@ "description": "User prompt to take action in order to save the login they just entered." }, "saveLogin": { - "message": "Save login", + "message": "ログインを保存", "description": "Prompt asking the user if they want to save their login details." }, "updateLogin": { - "message": "Update existing login", + "message": "既存のログイン情報を更新", "description": "Prompt asking the user if they want to update an existing login entry." }, "loginSaveSuccess": { @@ -1561,13 +1570,13 @@ "message": "セキュリティキーの読み取り" }, "readingPasskeyLoading": { - "message": "Reading passkey..." + "message": "パスキーを読み込み中…" }, "passkeyAuthenticationFailed": { - "message": "Passkey authentication failed" + "message": "パスキー認証に失敗しました" }, "useADifferentLogInMethod": { - "message": "Use a different log in method" + "message": "別のログイン方法を使用する" }, "awaitingSecurityKeyInteraction": { "message": "セキュリティキーとの通信を待ち受け中…" @@ -1636,7 +1645,7 @@ "message": "ベース サーバー URL または少なくとも 1 つのカスタム環境を追加する必要があります。" }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "URL は HTTPS を使用する必要があります。" }, "customEnvironment": { "message": "カスタム環境" @@ -1689,7 +1698,7 @@ } }, "turnOffAutofill": { - "message": "Turn off autofill" + "message": "自動入力をオフにする" }, "confirmAutofill": { "message": "Confirm autofill" @@ -1713,7 +1722,7 @@ "message": "Autofill without adding" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "自動入力しない" }, "showInlineMenuIdentitiesLabel": { "message": "ID を候補として表示する" @@ -1901,7 +1910,7 @@ "message": "セキュリティコード" }, "cardNumber": { - "message": "card number" + "message": "カード番号" }, "ex": { "message": "例:" @@ -2003,30 +2012,30 @@ "message": "SSH 鍵" }, "typeNote": { - "message": "Note" + "message": "メモ" }, "newItemHeaderLogin": { - "message": "New Login", + "message": "新規ログイン", "description": "Header for new login item type" }, "newItemHeaderCard": { - "message": "New Card", + "message": "新規カード", "description": "Header for new card item type" }, "newItemHeaderIdentity": { - "message": "New Identity", + "message": "新規身分証", "description": "Header for new identity item type" }, "newItemHeaderNote": { - "message": "New Note", + "message": "新規メモ", "description": "Header for new note item type" }, "newItemHeaderSshKey": { - "message": "New SSH key", + "message": "新しい SSH キー", "description": "Header for new SSH key item type" }, "newItemHeaderTextSend": { - "message": "New Text Send", + "message": "新しい Send テキスト", "description": "Header for new text send" }, "newItemHeaderFileSend": { @@ -2034,11 +2043,11 @@ "description": "Header for new file send" }, "editItemHeaderLogin": { - "message": "Edit Login", + "message": "ログインを編集", "description": "Header for edit login item type" }, "editItemHeaderCard": { - "message": "Edit Card", + "message": "カードを編集", "description": "Header for edit card item type" }, "editItemHeaderIdentity": { @@ -2046,15 +2055,15 @@ "description": "Header for edit identity item type" }, "editItemHeaderNote": { - "message": "Edit Note", + "message": "メモを編集", "description": "Header for edit note item type" }, "editItemHeaderSshKey": { - "message": "Edit SSH key", + "message": "SSH キーを編集", "description": "Header for edit SSH key item type" }, "editItemHeaderTextSend": { - "message": "Edit Text Send", + "message": "Send テキストを編集", "description": "Header for edit text send" }, "editItemHeaderFileSend": { @@ -2062,11 +2071,11 @@ "description": "Header for edit file send" }, "viewItemHeaderLogin": { - "message": "View Login", + "message": "ログインを表示", "description": "Header for view login item type" }, "viewItemHeaderCard": { - "message": "View Card", + "message": "カードを表示", "description": "Header for view card item type" }, "viewItemHeaderIdentity": { @@ -2074,11 +2083,11 @@ "description": "Header for view identity item type" }, "viewItemHeaderNote": { - "message": "View Note", + "message": "メモを表示", "description": "Header for view note item type" }, "viewItemHeaderSshKey": { - "message": "View SSH key", + "message": "SSH キーを表示", "description": "Header for view SSH key item type" }, "passwordHistory": { @@ -2337,7 +2346,7 @@ "message": "このパスワードを使用する" }, "useThisPassphrase": { - "message": "Use this passphrase" + "message": "このパスフレーズを使用" }, "useThisUsername": { "message": "このユーザー名を使用する" @@ -2654,7 +2663,7 @@ "message": "変更" }, "changePassword": { - "message": "Change password", + "message": "パスワードを変更", "description": "Change password button for browser at risk notification on login." }, "changeButtonTitle": { @@ -2667,7 +2676,7 @@ } }, "atRiskPassword": { - "message": "At-risk password" + "message": "リスクがあるパスワード" }, "atRiskPasswords": { "message": "リスクがあるパスワード" @@ -2843,7 +2852,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCountReached": { - "message": "Max access count reached", + "message": "最大アクセス数に達しました", "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "hideTextByDefault": { @@ -3193,7 +3202,7 @@ "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." }, "organizationName": { - "message": "Organization name" + "message": "組織名" }, "keyConnectorDomain": { "message": "Key Connector domain" @@ -3605,7 +3614,7 @@ "message": "リクエストが送信されました" }, "loginRequestApprovedForEmailOnDevice": { - "message": "Login request approved for $EMAIL$ on $DEVICE$", + "message": "$EMAIL$ に $DEVICE$ でのログインを承認しました", "placeholders": { "email": { "content": "$1", @@ -3618,16 +3627,16 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." + "message": "別のデバイスからのログイン試行を拒否しました。自分自身である場合は、もう一度デバイスでログインしてください。" }, "device": { - "message": "Device" + "message": "デバイス" }, "loginStatus": { - "message": "Login status" + "message": "ログイン状態" }, "masterPasswordChanged": { - "message": "Master password saved" + "message": "マスターパスワードが保存されました" }, "exposedMasterPassword": { "message": "流出したマスターパスワード" @@ -3720,28 +3729,28 @@ "message": "このデバイスを記憶して今後のログインをシームレスにする" }, "manageDevices": { - "message": "Manage devices" + "message": "デバイスを管理" }, "currentSession": { - "message": "Current session" + "message": "現在のセッション" }, "mobile": { - "message": "Mobile", + "message": "モバイル", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "拡張機能", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "デスクトップ", "description": "Desktop app" }, "webVault": { - "message": "Web vault" + "message": "ウェブ保管庫" }, "webApp": { - "message": "Web app" + "message": "Web アプリ" }, "cli": { "message": "CLI" @@ -3751,22 +3760,22 @@ "description": "Software Development Kit" }, "requestPending": { - "message": "Request pending" + "message": "保留中のリクエスト" }, "firstLogin": { - "message": "First login" + "message": "初回ログイン" }, "trusted": { - "message": "Trusted" + "message": "信頼済み" }, "needsApproval": { - "message": "Needs approval" + "message": "承認が必要" }, "devices": { - "message": "Devices" + "message": "デバイス" }, "accessAttemptBy": { - "message": "Access attempt by $EMAIL$", + "message": "$EMAIL$ によるログインの試行", "placeholders": { "email": { "content": "$1", @@ -3775,31 +3784,31 @@ } }, "confirmAccess": { - "message": "Confirm access" + "message": "アクセスの確認" }, "denyAccess": { - "message": "Deny access" + "message": "アクセスを拒否" }, "time": { - "message": "Time" + "message": "時間" }, "deviceType": { - "message": "Device Type" + "message": "デバイス種別" }, "loginRequest": { - "message": "Login request" + "message": "ログインリクエスト" }, "thisRequestIsNoLongerValid": { - "message": "This request is no longer valid." + "message": "このリクエストは無効になりました。" }, "loginRequestHasAlreadyExpired": { - "message": "Login request has already expired." + "message": "ログインリクエストの有効期限が切れています。" }, "justNow": { - "message": "Just now" + "message": "たった今" }, "requestedXMinutesAgo": { - "message": "Requested $MINUTES$ minutes ago", + "message": "$MINUTES$ 分前に要求されました", "placeholders": { "minutes": { "content": "$1", @@ -3829,7 +3838,7 @@ "message": "管理者の承認を要求する" }, "unableToCompleteLogin": { - "message": "Unable to complete login" + "message": "ログインを完了できません" }, "loginOnTrustedDeviceOrAskAdminToAssignPassword": { "message": "You need to log in on a trusted device or ask your administrator to assign you a password." @@ -3899,13 +3908,13 @@ "message": "Trust organization" }, "trust": { - "message": "Trust" + "message": "信頼する" }, "doNotTrust": { - "message": "Do not trust" + "message": "信頼しない" }, "organizationNotTrusted": { - "message": "Organization is not trusted" + "message": "組織は信頼されていません" }, "emergencyAccessTrustWarning": { "message": "For the security of your account, only confirm if you have granted emergency access to this user and their fingerprint matches what is displayed in their account" @@ -3920,11 +3929,11 @@ "message": "Trust user" }, "sendsTitleNoItems": { - "message": "Send sensitive information safely", + "message": "機密情報を安全に送信", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { - "message": "Share files and data securely with anyone, on any platform. Your information will remain end-to-end encrypted while limiting exposure.", + "message": "どのプラットフォームでも、誰とでも安全にファイルとデータを共有できます。流出を防止しながら、あなたの情報はエンドツーエンドで暗号化されます。", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "inputRequired": { @@ -4051,13 +4060,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "自動入力できません" }, "cannotAutofillExactMatch": { "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." }, "okay": { - "message": "Okay" + "message": "OK" }, "toggleSideNavigation": { "message": "サイドナビゲーションの切り替え" @@ -4268,10 +4277,10 @@ "message": "コレクションを選択" }, "importTargetHintCollection": { - "message": "Select this option if you want the imported file contents moved to a collection" + "message": "インポートしたファイルコンテンツをコレクションに移動したい場合は、このオプションを選択してください" }, "importTargetHintFolder": { - "message": "Select this option if you want the imported file contents moved to a folder" + "message": "インポートしたファイルコンテンツをフォルダーに移動したい場合は、このオプションを選択してください" }, "importUnassignedItemsError": { "message": "割り当てられていないアイテムがファイルに含まれています。" @@ -4524,7 +4533,7 @@ "description": "Link to match detection docs on warning dialog for advance match strategy" }, "uriAdvancedOption": { - "message": "Advanced options", + "message": "高度な設定", "description": "Advanced option placeholder for uri option component" }, "confirmContinueToBrowserSettingsTitle": { @@ -4711,7 +4720,7 @@ } }, "copyFieldCipherName": { - "message": "Copy $FIELD$, $CIPHERNAME$", + "message": "$FIELD$ ($CIPHERNAME$) をコピー", "description": "Title for a button that copies a field value to the clipboard.", "placeholders": { "field": { @@ -4858,31 +4867,31 @@ } }, "downloadBitwarden": { - "message": "Download Bitwarden" + "message": "Bitwarden をダウンロード" }, "downloadBitwardenOnAllDevices": { - "message": "Download Bitwarden on all devices" + "message": "すべてのデバイスに Bitwarden をダウンロード" }, "getTheMobileApp": { - "message": "Get the mobile app" + "message": "モバイルアプリを入手" }, "getTheMobileAppDesc": { "message": "Access your passwords on the go with the Bitwarden mobile app." }, "getTheDesktopApp": { - "message": "Get the desktop app" + "message": "デスクトップアプリを入手" }, "getTheDesktopAppDesc": { "message": "Access your vault without a browser, then set up unlock with biometrics to expedite unlocking in both the desktop app and browser extension." }, "downloadFromBitwardenNow": { - "message": "Download from bitwarden.com now" + "message": "bitwarden.com から今すぐダウンロード" }, "getItOnGooglePlay": { - "message": "Get it on Google Play" + "message": "Google Play で入手" }, "downloadOnTheAppStore": { - "message": "Download on the App Store" + "message": "App Store からダウンロード" }, "permanentlyDeleteAttachmentConfirmation": { "message": "この添付ファイルを完全に削除してもよろしいですか?" @@ -4975,7 +4984,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "既定 ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5248,7 +5257,7 @@ "message": "拡張機能アイコンにログイン自動入力の候補の数を表示する" }, "accountAccessRequested": { - "message": "Account access requested" + "message": "アカウントへのアクセスが要求されました" }, "confirmAccessAttempt": { "message": "Confirm access attempt for $EMAIL$", @@ -5365,7 +5374,7 @@ "message": "Unlock PIN set" }, "unlockWithBiometricSet": { - "message": "Unlock with biometrics set" + "message": "生体認証でロック解除を設定しました" }, "authenticating": { "message": "認証中" @@ -5379,7 +5388,7 @@ "description": "Notification message for when a password has been regenerated" }, "saveToBitwarden": { - "message": "Save to Bitwarden", + "message": "Bitwarden へ保存", "description": "Confirmation message for saving a login to Bitwarden" }, "spaceCharacterDescriptor": { @@ -5587,13 +5596,13 @@ "message": "This login is at-risk and missing a website. Add a website and change the password for stronger security." }, "missingWebsite": { - "message": "Missing website" + "message": "ウェブサイトがありません" }, "settingsVaultOptions": { "message": "保管庫オプション" }, "emptyVaultDescription": { - "message": "The vault protects more than just your passwords. Store secure logins, IDs, cards and notes securely here." + "message": "保管庫はパスワードだけではなく、ログイン情報、ID、カード、メモを安全に保管できます。" }, "introCarouselLabel": { "message": "Bitwarden へようこそ" @@ -5623,19 +5632,19 @@ "message": "Bitwarden のモバイル、ブラウザ、デスクトップアプリでは、保存できるパスワード数やデバイス数に制限はありません。" }, "nudgeBadgeAria": { - "message": "1 notification" + "message": "1件の通知" }, "emptyVaultNudgeTitle": { - "message": "Import existing passwords" + "message": "既存のパスワードをインポート" }, "emptyVaultNudgeBody": { "message": "Use the importer to quickly transfer logins to Bitwarden without manually adding them." }, "emptyVaultNudgeButton": { - "message": "Import now" + "message": "今すぐインポート" }, "hasItemsVaultNudgeTitle": { - "message": "Welcome to your vault!" + "message": "保管庫へようこそ!" }, "phishingPageTitleV2": { "message": "Phishing attempt detected" @@ -5644,7 +5653,7 @@ "message": "The site you are attempting to visit is a known malicious site and a security risk." }, "phishingPageCloseTabV2": { - "message": "Close this tab" + "message": "このタブを閉じる" }, "phishingPageContinueV2": { "message": "Continue to this site (not recommended)" @@ -5661,7 +5670,7 @@ "message": "Learn more about phishing detection" }, "protectedBy": { - "message": "Protected by $PRODUCT$", + "message": "$PRODUCT$ によって保護されています", "placeholders": { "product": { "content": "$1", @@ -5687,7 +5696,7 @@ "example": "Include a Website so this login appears as an autofill suggestion." }, "newLoginNudgeBodyBold": { - "message": "Website", + "message": "ウェブサイト", "description": "This is in multiple parts to allow for bold text in the middle of the sentence.", "example": "Include a Website so this login appears as an autofill suggestion." }, @@ -5715,20 +5724,20 @@ "message": "With notes, securely store sensitive data like banking or insurance details." }, "newSshNudgeTitle": { - "message": "Developer-friendly SSH access" + "message": "開発者フレンドリーの SSH アクセス" }, "newSshNudgeBodyOne": { - "message": "Store your keys and connect with the SSH agent for fast, encrypted authentication.", + "message": "SSHエージェントにキーを登録することで、高速かつ暗号化された認証が可能になります。", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "newSshNudgeBodyTwo": { - "message": "Learn more about SSH agent", + "message": "SSH エージェントに関する詳細", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" }, "generatorNudgeTitle": { - "message": "Quickly create passwords" + "message": "パスワードをすばやく作成" }, "generatorNudgeBodyOne": { "message": "Easily create strong and unique passwords by clicking on", @@ -5745,7 +5754,7 @@ "description": "Aria label for the body content of the generator nudge" }, "aboutThisSetting": { - "message": "About this setting" + "message": "この設定について" }, "permitCipherDetailsDescription": { "message": "Bitwarden will use saved login URIs to identify which icon or change password URL should be used to improve your experience. No information is collected or saved when you use this service." @@ -5758,13 +5767,13 @@ "description": "'WebAssembly' is a technical term and should not be translated." }, "showMore": { - "message": "Show more" + "message": "もっと見る" }, "showLess": { - "message": "Show less" + "message": "隠す" }, "next": { - "message": "Next" + "message": "次へ" }, "moreBreadcrumbs": { "message": "More breadcrumbs", @@ -5777,16 +5786,16 @@ "message": "Great job securing your at-risk logins!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "今すぐアップグレード" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "認証機を内蔵" }, "secureFileStorage": { "message": "Secure file storage" }, "emergencyAccess": { - "message": "Emergency access" + "message": "緊急アクセス" }, "breachMonitoring": { "message": "Breach monitoring" @@ -5798,16 +5807,25 @@ "message": "Complete online security" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "プレミアムにアップグレード" + }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "郵便番号" }, "cardNumberLabel": { - "message": "Card number" + "message": "カード番号" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index eaa5bc43021..5c7a8da23a7 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index e5adcfce833..3e929bc6533 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "ಸಿಸ್ಟಮ್ ಲಾಕ್‌ನಲ್ಲಿ" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "ಬ್ರೌಸರ್ ಮರುಪ್ರಾರಂಭದಲ್ಲಿ" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 6037d208b42..5a21928c233 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "로그인 보기" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "시스템 잠금 시" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "브라우저 재시작 시" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 8e858de4f47..ac598394a8c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Užrakinant sistemą" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Paleidus iš naujo naršyklę" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index a4be22d433a..70f46e0f068 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Apskatīt visu" }, + "viewLess": { + "message": "Skatīt mazāk" + }, "viewLogin": { "message": "Apskatīt pieteikšanās vienumu" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Pēc sistēmas aizslēgšanas" }, + "onIdle": { + "message": "Sistēmas dīkstāvē" + }, + "onSleep": { + "message": "Pēc sistēmas iemigšanas" + }, "onRestart": { "message": "Pēc pārlūka pārsāknēšanas" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Uzlabot uz Premium" }, + "loadingVault": { + "message": "Ielādē glabātavu" + }, + "vaultLoaded": { + "message": "Glabātava ielādēta" + }, "settingDisabledByPolicy": { "message": "Šis iestatījums ir atspējots apvienības pamatnostādnēs.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kartes numurs" + }, + "sessionTimeoutSettingsAction": { + "message": "Noildzes darbība" } } diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index cda9ec03923..d139531315b 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "സിസ്റ്റം ലോക്കിൽ" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "ബ്രൌസർ പുനരാരംഭിക്കുമ്പോൾ" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 57624a82381..438cc750557 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 1268c960c8f..4ae8a01a12f 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Ved maskinlåsing" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Ved nettleseromstart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index 441ea71d840..b3463c9f1b3 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Alles weergeven" }, + "viewLess": { + "message": "Minder weergeven" + }, "viewLogin": { "message": "Login bekijken" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Bij systeemvergrendeling" }, + "onIdle": { + "message": "Bij systeeminactiviteit" + }, + "onSleep": { + "message": "Bij slaapmodus" + }, "onRestart": { "message": "Bij herstart van de browser" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Opwaarderen naar Premium" }, + "loadingVault": { + "message": "Kluis laden" + }, + "vaultLoaded": { + "message": "Kluis geladen" + }, "settingDisabledByPolicy": { "message": "Deze instelling is uitgeschakeld door het beleid van uw organisatie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kaartnummer" + }, + "sessionTimeoutSettingsAction": { + "message": "Time-out actie" } } diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 1047ac9466e..77b6cc436d7 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Pokaż dane logowania" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Po zablokowaniu urządzenia" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Po uruchomieniu przeglądarki" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Numer karty" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index a4da9025a8e..c3d96145944 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Ver tudo" }, + "viewLess": { + "message": "Ver menos" + }, "viewLogin": { "message": "Ver credencial" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Ao bloquear o sistema" }, + "onIdle": { + "message": "Quando o sistema ficar inativo" + }, + "onSleep": { + "message": "Quando o sistema hibernar" + }, "onRestart": { "message": "Ao reiniciar o navegador" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Faça upgrade para o Premium" }, + "loadingVault": { + "message": "Carregando cofre" + }, + "vaultLoaded": { + "message": "Cofre carregado" + }, "settingDisabledByPolicy": { "message": "Essa configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Número do cartão" + }, + "sessionTimeoutSettingsAction": { + "message": "Ação do tempo limite" } } diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 15c993ab768..10fbc3db004 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Ver tudo" }, + "viewLess": { + "message": "Ver menos" + }, "viewLogin": { "message": "Ver credencial" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Ao bloquear o sistema" }, + "onIdle": { + "message": "Na inatividade do sistema" + }, + "onSleep": { + "message": "Na suspensão do sistema" + }, "onRestart": { "message": "Ao reiniciar o navegador" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Atualizar para o Premium" }, + "loadingVault": { + "message": "A carregar o cofre" + }, + "vaultLoaded": { + "message": "Cofre carregado" + }, "settingDisabledByPolicy": { "message": "Esta configuração está desativada pela política da sua organização.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Número do cartão" + }, + "sessionTimeoutSettingsAction": { + "message": "Ação de tempo limite" } } diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 4e1ac8ae832..5fe7c61f9cc 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "La blocarea sistemului" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "La repornirea browserului" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index d59fc34f736..349e68c5194 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Посмотреть все" }, + "viewLess": { + "message": "Свернуть" + }, "viewLogin": { "message": "Просмотр логина" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Вместе с компьютером" }, + "onIdle": { + "message": "При бездействии" + }, + "onSleep": { + "message": "В режиме сна" + }, "onRestart": { "message": "При перезапуске браузера" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Обновить до Премиум" }, + "loadingVault": { + "message": "Загрузка хранилища" + }, + "vaultLoaded": { + "message": "Хранилище загружено" + }, "settingDisabledByPolicy": { "message": "Этот параметр отключен политикой вашей организации.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Номер карты" + }, + "sessionTimeoutSettingsAction": { + "message": "Тайм-аут действия" } } diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 8c5961153eb..9b36684dc5a 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "පද්ධතිය ලොක් මත" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "බ්රව්සරය නැවත ආරම්භ" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index b459c86c236..a269756a414 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Zobraziť všetky" }, + "viewLess": { + "message": "Zobraziť menej" + }, "viewLogin": { "message": "Zobraziť prihlásenie" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Keď je systém uzamknutý" }, + "onIdle": { + "message": "Pri nečinnosti systému" + }, + "onSleep": { + "message": "V režime spánku" + }, "onRestart": { "message": "Po reštarte prehliadača" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgradovať na Prémium" }, + "loadingVault": { + "message": "Načítava sa trezor" + }, + "vaultLoaded": { + "message": "Trezor sa načítal" + }, "settingDisabledByPolicy": { "message": "Politika organizácie vypla toto nastavenie.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Číslo karty" + }, + "sessionTimeoutSettingsAction": { + "message": "Akcia pri vypršaní časového limitu" } } diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 0a6266636b3..3cbd9a11342 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Ob zaklepu sistema" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Ob ponovnem zagonu brskalnika" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 0158ca6ba2b..d13939f8656 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -32,7 +32,7 @@ "message": "Употребити једнократну пријаву" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша организација захтева јединствену пријаву." }, "welcomeBack": { "message": "Добродошли назад" @@ -592,7 +592,10 @@ "message": "Приказ" }, "viewAll": { - "message": "View all" + "message": "Прегледај све" + }, + "viewLess": { + "message": "View less" }, "viewLogin": { "message": "Преглед пријаве" @@ -796,6 +799,12 @@ "onLocked": { "message": "На закључавање система" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "На покретање прегледача" }, @@ -1035,10 +1044,10 @@ "message": "Ставка уређена" }, "savedWebsite": { - "message": "Saved website" + "message": "Сачувана веб локација" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Сачувана веб локација ($COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1636,7 +1645,7 @@ "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Везе морају да користе HTTPS." }, "customEnvironment": { "message": "Прилагођено окружење" @@ -1692,28 +1701,28 @@ "message": "Угасити ауто-пуњење" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Потврди аутопуњење" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Овај сајт се не подудара са вашим сачуваним подацима за пријаву. Пре него што унесете своје акредитиве за пријаву, уверите се да је то поуздан сајт." }, "showInlineMenuLabel": { "message": "Прикажи предлоге за ауто-попуњавање у пољима обрасца" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Како Bitwarden штити ваше податке од фишинга?" }, "currentWebsite": { - "message": "Current website" + "message": "Тренутни сајт" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Ауто-попуни и додај овај сајт" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Ауто-попуни без додавања" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Не попуни" }, "showInlineMenuIdentitiesLabel": { "message": "Приказати идентитете као предлоге" @@ -3277,7 +3286,7 @@ "message": "Грешка при декрипцији" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Грешка при преузимању података за ауто-попуњавање" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden није могао да декриптује ставке из трезора наведене испод." @@ -4051,13 +4060,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Не може да се ауто-попуни" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "Подразумевано подударање је подешено на „Тачно подударање“. Тренутна веб локација не одговара тачно сачуваним детаљима за пријаву за ову ставку." }, "okay": { - "message": "Okay" + "message": "У реду" }, "toggleSideNavigation": { "message": "Укључите бочну навигацију" @@ -4975,7 +4984,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Подразумевано ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5774,40 +5783,49 @@ "message": "Потврдите домен конектора кључа" }, "atRiskLoginsSecured": { - "message": "Great job securing your at-risk logins!" + "message": "Сјајан посао обезбеђивања ваших ризичних пријава!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Надогради сада" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Уграђени аутентификатор" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Сигурно складиштење датотека" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Хитан приступ" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Праћење повreda безбедности" }, "andMoreFeatures": { - "message": "And more!" + "message": "И још више!" }, "planDescPremium": { - "message": "Complete online security" + "message": "Потпуна онлајн безбедност" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Надоградите на Premium" + }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" }, "settingDisabledByPolicy": { - "message": "This setting is disabled by your organization's policy.", + "message": "Ово подешавање је онемогућено смерницама ваше организације.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "ZIP/Поштански број" }, "cardNumberLabel": { - "message": "Card number" + "message": "Број картице" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 057a7ca746c..9f84e9d714c 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Visa alla" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Visa inloggning" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Vid låsning av datorn" }, + "onIdle": { + "message": "När systemet är overksamt" + }, + "onSleep": { + "message": "När systemet är i strömsparläge" + }, "onRestart": { "message": "Vid omstart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Uppgradera till Premium" }, + "loadingVault": { + "message": "Läser in valv" + }, + "vaultLoaded": { + "message": "Valvet lästes in" + }, "settingDisabledByPolicy": { "message": "Denna inställning är inaktiverad enligt din organisations policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kortnummer" + }, + "sessionTimeoutSettingsAction": { + "message": "Tidsgränsåtgärd" } } diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index 43944875889..cbefd26424c 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "உள்நுழைவைக் காண்க" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "சிஸ்டம் பூட்டப்பட்டவுடன்" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "உலாவி மறுதொடக்கம் செய்யப்பட்டவுடன்" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 39e6c0be881..13e74f8d807 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On system lock" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On browser restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index e92192dafa0..594bc6d7a94 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "View all" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "View login" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "On Locked" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "On Restart" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Card number" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 543560810fe..7f234b8750a 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Tümünü göster" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Hesabı göster" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Sistem kilitlenince" }, + "onIdle": { + "message": "Sistem boştayken" + }, + "onSleep": { + "message": "Sistem uyuyunca" + }, "onRestart": { "message": "Tarayıcı yeniden başlatılınca" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Premium'a yükselt" }, + "loadingVault": { + "message": "Kasa yükleniyor" + }, + "vaultLoaded": { + "message": "Kasa yüklendi" + }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Kart numarası" + }, + "sessionTimeoutSettingsAction": { + "message": "Zaman aşımı eylemi" } } diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 2c6fa4eb15b..a17033ee6e8 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Переглянути все" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Переглянути запис" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "З блокуванням системи" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "З перезапуском браузера" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Покращити до Premium" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "Цей параметр вимкнено політикою вашої організації.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Номер картки" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 8029f5b2c46..2fdba62adeb 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "Xem tất cả" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "Xem đăng nhập" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "Mỗi khi khóa máy" }, + "onIdle": { + "message": "On system idle" + }, + "onSleep": { + "message": "On system sleep" + }, "onRestart": { "message": "Mỗi khi khởi động lại trình duyệt" }, @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" }, + "loadingVault": { + "message": "Loading vault" + }, + "vaultLoaded": { + "message": "Vault loaded" + }, "settingDisabledByPolicy": { "message": "Cài đặt này bị vô hiệu hóa bởi chính sách tổ chức của bạn.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "Số thẻ" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" } } diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index e59a74e358d..52d8a03b769 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "查看全部" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "查看登录" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "系统锁定时" }, + "onIdle": { + "message": "系统空闲时" + }, + "onSleep": { + "message": "系统睡眠时" + }, "onRestart": { "message": "浏览器重启时" }, @@ -3726,7 +3735,7 @@ "message": "当前会话" }, "mobile": { - "message": "移动", + "message": "移动端", "description": "Mobile app" }, "extension": { @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "升级为高级版" }, + "loadingVault": { + "message": "正在加载密码库" + }, + "vaultLoaded": { + "message": "密码库已加载" + }, "settingDisabledByPolicy": { "message": "此设置被您组织的策略禁用了。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "卡号" + }, + "sessionTimeoutSettingsAction": { + "message": "超时动作" } } diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 63f3ea59f60..370c147871b 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -594,6 +594,9 @@ "viewAll": { "message": "檢視全部" }, + "viewLess": { + "message": "View less" + }, "viewLogin": { "message": "檢視登入" }, @@ -796,6 +799,12 @@ "onLocked": { "message": "於系統鎖定時" }, + "onIdle": { + "message": "系統閒置時" + }, + "onSleep": { + "message": "系統睡眠時" + }, "onRestart": { "message": "於瀏覽器重新啟動時" }, @@ -4975,7 +4984,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "預設 ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5800,6 +5809,12 @@ "upgradeToPremium": { "message": "升級到 Premium" }, + "loadingVault": { + "message": "正在載入密碼庫" + }, + "vaultLoaded": { + "message": "已載入密碼庫" + }, "settingDisabledByPolicy": { "message": "此設定已被你的組織原則停用。", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." @@ -5809,5 +5824,8 @@ }, "cardNumberLabel": { "message": "支付卡號碼" + }, + "sessionTimeoutSettingsAction": { + "message": "逾時後動作" } } From 1ce33a0a985ac4ab17266eb040003fab7f735bc8 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:44:13 +0100 Subject: [PATCH 133/249] Autosync the updated translations (#17377) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/af/messages.json | 9 +- apps/desktop/src/locales/ar/messages.json | 9 +- apps/desktop/src/locales/az/messages.json | 17 +- apps/desktop/src/locales/be/messages.json | 9 +- apps/desktop/src/locales/bg/messages.json | 9 +- apps/desktop/src/locales/bn/messages.json | 9 +- apps/desktop/src/locales/bs/messages.json | 9 +- apps/desktop/src/locales/ca/messages.json | 45 ++-- apps/desktop/src/locales/cs/messages.json | 9 +- apps/desktop/src/locales/cy/messages.json | 9 +- apps/desktop/src/locales/da/messages.json | 9 +- apps/desktop/src/locales/de/messages.json | 13 +- apps/desktop/src/locales/el/messages.json | 9 +- apps/desktop/src/locales/en_GB/messages.json | 9 +- apps/desktop/src/locales/en_IN/messages.json | 9 +- apps/desktop/src/locales/eo/messages.json | 9 +- apps/desktop/src/locales/es/messages.json | 9 +- apps/desktop/src/locales/et/messages.json | 9 +- apps/desktop/src/locales/eu/messages.json | 9 +- apps/desktop/src/locales/fa/messages.json | 9 +- apps/desktop/src/locales/fi/messages.json | 9 +- apps/desktop/src/locales/fil/messages.json | 9 +- apps/desktop/src/locales/fr/messages.json | 11 +- apps/desktop/src/locales/gl/messages.json | 9 +- apps/desktop/src/locales/he/messages.json | 9 +- apps/desktop/src/locales/hi/messages.json | 9 +- apps/desktop/src/locales/hr/messages.json | 9 +- apps/desktop/src/locales/hu/messages.json | 9 +- apps/desktop/src/locales/id/messages.json | 9 +- apps/desktop/src/locales/it/messages.json | 9 +- apps/desktop/src/locales/ja/messages.json | 9 +- apps/desktop/src/locales/ka/messages.json | 9 +- apps/desktop/src/locales/km/messages.json | 9 +- apps/desktop/src/locales/kn/messages.json | 9 +- apps/desktop/src/locales/ko/messages.json | 9 +- apps/desktop/src/locales/lt/messages.json | 9 +- apps/desktop/src/locales/lv/messages.json | 9 +- apps/desktop/src/locales/me/messages.json | 9 +- apps/desktop/src/locales/ml/messages.json | 9 +- apps/desktop/src/locales/mr/messages.json | 9 +- apps/desktop/src/locales/my/messages.json | 9 +- apps/desktop/src/locales/nb/messages.json | 9 +- apps/desktop/src/locales/ne/messages.json | 9 +- apps/desktop/src/locales/nl/messages.json | 9 +- apps/desktop/src/locales/nn/messages.json | 9 +- apps/desktop/src/locales/or/messages.json | 9 +- apps/desktop/src/locales/pl/messages.json | 9 +- apps/desktop/src/locales/pt_BR/messages.json | 241 ++++++++++--------- apps/desktop/src/locales/pt_PT/messages.json | 11 +- apps/desktop/src/locales/ro/messages.json | 9 +- apps/desktop/src/locales/ru/messages.json | 9 +- apps/desktop/src/locales/si/messages.json | 9 +- apps/desktop/src/locales/sk/messages.json | 47 ++-- apps/desktop/src/locales/sl/messages.json | 9 +- apps/desktop/src/locales/sr/messages.json | 41 ++-- apps/desktop/src/locales/sv/messages.json | 9 +- apps/desktop/src/locales/ta/messages.json | 9 +- apps/desktop/src/locales/te/messages.json | 9 +- apps/desktop/src/locales/th/messages.json | 9 +- apps/desktop/src/locales/tr/messages.json | 9 +- apps/desktop/src/locales/uk/messages.json | 9 +- apps/desktop/src/locales/vi/messages.json | 9 +- apps/desktop/src/locales/zh_CN/messages.json | 11 +- apps/desktop/src/locales/zh_TW/messages.json | 11 +- 64 files changed, 563 insertions(+), 371 deletions(-) diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json index 6da1c7e9c8b..1c6a2bc49c9 100644 --- a/apps/desktop/src/locales/af/messages.json +++ b/apps/desktop/src/locales/af/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Ongelukkig word blaaierintegrasie tans slegs in die weergawe vir die Mac-toepwinkel ondersteun." - }, "browserIntegrationWindowsStoreDesc": { "message": "Ongelukkig word blaaierintegrasie tans nie in die weergawe vir die Windows-winkel ondersteun nie." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json index a6a7e881db9..ca404f4e179 100644 --- a/apps/desktop/src/locales/ar/messages.json +++ b/apps/desktop/src/locales/ar/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "حدث خطأ أثناء تمكين دمج المتصفح." }, - "browserIntegrationMasOnlyDesc": { - "message": "للأسف، لا يتم دعم تكامل المتصفح إلا في إصدار متجر تطبيقات ماك في الوقت الحالي." - }, "browserIntegrationWindowsStoreDesc": { "message": "للأسف، لا يتم دعم تكامل المتصفح في إصدار متجر ويندوز في الوقت الحالي." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json index 81b24cadfe8..55c2bdcd677 100644 --- a/apps/desktop/src/locales/az/messages.json +++ b/apps/desktop/src/locales/az/messages.json @@ -511,7 +511,7 @@ "description": "This describes a value that is 'linked' (related) to another value." }, "remove": { - "message": "Çıxart" + "message": "Xaric et" }, "nameRequired": { "message": "Ad lazımdır." @@ -1659,7 +1659,7 @@ } }, "passwordSafe": { - "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz." + "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Əmniyyətlə istifadə edə bilərsiniz." }, "baseDomain": { "message": "Baza domeni", @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Brauzer inteqrasiyasını fəallaşdırarkən bir xəta baş verdi." }, - "browserIntegrationMasOnlyDesc": { - "message": "Təəssüf ki, brauzer inteqrasiyası indilik yalnız Mac App Store versiyasında dəstəklənir." - }, "browserIntegrationWindowsStoreDesc": { "message": "Təəssüf ki, brauzer inteqrasiyası hal-hazırda Windows Store versiyasında dəstəklənmir." }, @@ -3906,7 +3903,7 @@ "message": "Ana qovluğun adından sonra \"/\" əlavə edərək qovluğu ardıcıl yerləşdirin. Nümunə: Social/Forums" }, "sendsTitleNoItems": { - "message": "Send, həssas məlumatlar təhlükəsizdir", + "message": "Send ilə həssas məlumatlar əmniyyətdədir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { @@ -3961,7 +3958,7 @@ "message": "Kimliklərinizlə, uzun qeydiyyat və ya əlaqə xanalarını daha tez avtomatik doldurun." }, "newNoteNudgeTitle": { - "message": "Həssas verilərinizi güvənli şəkildə saxlayın" + "message": "Həssas verilərinizi əmniyyətdə saxlayın" }, "newNoteNudgeBody": { "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas veriləri təhlükəsiz saxlayın." @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" + }, + "sessionTimeoutSettingsAction": { + "message": "Vaxt bitmə əməliyyatı" + }, + "sessionTimeoutHeader": { + "message": "Sessiya vaxt bitməsi" } } diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json index cead61915ca..b2e4db47b32 100644 --- a/apps/desktop/src/locales/be/messages.json +++ b/apps/desktop/src/locales/be/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "На жаль, інтэграцыя з браўзерам зараз падтрымліваецца толькі ў версіі для Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "На жаль, інтэграцыя з браўзерам у цяперашні час не падтрымліваецца ў версіі для Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json index c2c2e236d37..ad03c2cc023 100644 --- a/apps/desktop/src/locales/bg/messages.json +++ b/apps/desktop/src/locales/bg/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Възникна грешка при включването на интеграцията с браузъра." }, - "browserIntegrationMasOnlyDesc": { - "message": "За жалост в момента интеграцията с браузър не се поддържа във версията за магазина на Mac." - }, "browserIntegrationWindowsStoreDesc": { "message": "За жалост в момента интеграцията с браузър не се поддържа във версията за магазина на Windows." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Надградете до Платения план" + }, + "sessionTimeoutSettingsAction": { + "message": "Действие при изтичането на времето за достъп" + }, + "sessionTimeoutHeader": { + "message": "Изтичане на времето за сесията" } } diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json index 5c932d4ed21..d6c61c1ab51 100644 --- a/apps/desktop/src/locales/bn/messages.json +++ b/apps/desktop/src/locales/bn/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json index 08793959da6..569f1072c4b 100644 --- a/apps/desktop/src/locales/bs/messages.json +++ b/apps/desktop/src/locales/bs/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Nažalost, za sada je integracija sa preglednikom podržana samo u Mac App Store verziji aplikacije." - }, "browserIntegrationWindowsStoreDesc": { "message": "Nažalost, integracija sa preglednikom nije podržana u Windows Store verziji aplikacije." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json index 8ee8f7030bc..de468f1e8b3 100644 --- a/apps/desktop/src/locales/ca/messages.json +++ b/apps/desktop/src/locales/ca/messages.json @@ -42,7 +42,7 @@ "message": "Cerca en la caixa forta" }, "resetSearch": { - "message": "Reset search" + "message": "Restableix la cerca" }, "addItem": { "message": "Afegeix element" @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "No teniu permisos per editar aquest element" }, "welcomeBack": { "message": "Benvingut/da de nou" @@ -706,10 +706,10 @@ "message": "S'ha guardat el fitxer adjunt." }, "addAttachment": { - "message": "Add attachment" + "message": "Afig adjunt" }, "maxFileSizeSansPunctuation": { - "message": "Maximum file size is 500 MB" + "message": "La mida màxima del fitxer és de 500 MB" }, "file": { "message": "Fitxer" @@ -757,7 +757,7 @@ "message": "Inicia sessió a Bitwarden" }, "enterTheCodeSentToYourEmail": { - "message": "Enter the code sent to your email" + "message": "Introduïu el codi que us hem enviat al correu electrònic" }, "enterTheCodeFromYourAuthenticatorApp": { "message": "Introduïu el codi de la vostra aplicació d'autenticació" @@ -954,14 +954,14 @@ } }, "dontAskAgainOnThisDeviceFor30Days": { - "message": "Don't ask again on this device for 30 days" + "message": "No ho torneu a preguntar en aquest dispositiu durant 30 dies" }, "selectAnotherMethod": { "message": "Select another method", "description": "Select another two-step login method" }, "useYourRecoveryCode": { - "message": "Use your recovery code" + "message": "Utilitzeu el codi de recuperació" }, "insertU2f": { "message": "Introduïu la vostra clau de seguretat al port USB de l'ordinador. Si té un botó, premeu-lo." @@ -1467,7 +1467,7 @@ "description": "Copy credit card security code (CVV)" }, "cardNumber": { - "message": "card number" + "message": "núm. targeta de crèdit" }, "premiumMembership": { "message": "Subscripció Premium" @@ -1862,10 +1862,10 @@ "message": "Bloqueja amb la contrasenya mestra en reiniciar" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "Sol·licita la contrasenya mestra o el PIN en reiniciar l'aplicació" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "Sol·licita la contrasenya mestra en reiniciar l'aplicació" }, "deleteAccount": { "message": "Suprimeix el compte" @@ -2023,7 +2023,7 @@ "message": "Make 2-step verification seamless" }, "totpHelper": { - "message": "Bitwarden can store and fill 2-step verification codes. Copy and paste the key into this field." + "message": "Bitwarden pot emmagatzemar i omplir codis de verificació en dos passos. Copieu i enganxeu la clau en aquest camp." }, "totpHelperWithCapture": { "message": "Bitwarden can store and fill 2-step verification codes. Select the camera icon to take a screenshot of this website's authenticator QR code, or copy and paste the key into this field." @@ -2048,7 +2048,7 @@ } }, "cardExpiredTitle": { - "message": "Expired card" + "message": "Targeta de crèdit caducada" }, "cardExpiredMessage": { "message": "If you've renewed it, update the card's information" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "S'ha produït un error en activar la integració del navegador." }, - "browserIntegrationMasOnlyDesc": { - "message": "Malauradament, la integració del navegador només és compatible amb la versió de Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Malauradament, la integració del navegador només és compatible amb la versió de Microsoft Store." }, @@ -3092,18 +3089,18 @@ "message": "You denied a login attempt from another device. If this was you, try to log in with the device again." }, "webApp": { - "message": "Web app" + "message": "Aplicació web" }, "mobile": { - "message": "Mobile", + "message": "Mòbil", "description": "Mobile app" }, "extension": { - "message": "Extension", + "message": "Extensió", "description": "Browser extension/addon" }, "desktop": { - "message": "Desktop", + "message": "Escriptori", "description": "Desktop app" }, "cli": { @@ -3114,10 +3111,10 @@ "description": "Software Development Kit" }, "server": { - "message": "Server" + "message": "Servidor" }, "loginRequest": { - "message": "Login request" + "message": "Petició d'inici de sessió" }, "deviceType": { "message": "Tipus de dispositiu" @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json index 578d0607cc2..c02dbabbc93 100644 --- a/apps/desktop/src/locales/cs/messages.json +++ b/apps/desktop/src/locales/cs/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Vyskytla se chyba při povolování integrace prohlížeče." }, - "browserIntegrationMasOnlyDesc": { - "message": "Integrace prohlížeče je podporována jen ve verzi pro Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Integrace prohlížeče není ve verzi pro Windows Store podporována." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Aktualizovat na Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Akce vypršení časového limitu" + }, + "sessionTimeoutHeader": { + "message": "Časový limit relace" } } diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json index 278196f9d04..25b52fcc101 100644 --- a/apps/desktop/src/locales/cy/messages.json +++ b/apps/desktop/src/locales/cy/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json index fad9b9c1af4..1d135a533f2 100644 --- a/apps/desktop/src/locales/da/messages.json +++ b/apps/desktop/src/locales/da/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "En fejl opstod under aktivering af webbrowserintegration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Desværre understøttes browserintegration indtil videre kun i Mac App Store-versionen." - }, "browserIntegrationWindowsStoreDesc": { "message": "Desværre understøttes browserintegration pt. ikke i Microsoft Store-versionen." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json index 3f5caa00c4c..2f8daec5b68 100644 --- a/apps/desktop/src/locales/de/messages.json +++ b/apps/desktop/src/locales/de/messages.json @@ -772,7 +772,7 @@ "message": "Anmelden mit einem anderen Gerät" }, "useSingleSignOn": { - "message": "Single Sign-on verwenden" + "message": "Single Sign-On verwenden" }, "yourOrganizationRequiresSingleSignOn": { "message": "Deine Organisation erfordert Single Sign-On." @@ -1979,7 +1979,7 @@ "message": "Timeout-Aktion bestätigen" }, "enterpriseSingleSignOn": { - "message": "Enterprise Single-Sign-On" + "message": "Enterprise Single Sign-On" }, "setMasterPassword": { "message": "Master-Passwort festlegen" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Beim Aktivieren der Browser-Integration ist ein Fehler aufgetreten." }, - "browserIntegrationMasOnlyDesc": { - "message": "Leider wird die Browser-Integration derzeit nur in der Mac App Store Version unterstützt." - }, "browserIntegrationWindowsStoreDesc": { "message": "Leider wird die Browser-Integration derzeit nicht in der Microsoft Store Version unterstützt." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade auf Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout-Aktion" + }, + "sessionTimeoutHeader": { + "message": "Sitzungs-Timeout" } } diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json index 55a3c4fe170..0b869c1e02f 100644 --- a/apps/desktop/src/locales/el/messages.json +++ b/apps/desktop/src/locales/el/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Παρουσιάστηκε σφάλμα κατά την ενεργοποίηση ενσωμάτωσης του περιηγητή." }, - "browserIntegrationMasOnlyDesc": { - "message": "Δυστυχώς η ενσωμάτωση του προγράμματος περιήγησης υποστηρίζεται μόνο στην έκδοση Mac App Store για τώρα." - }, "browserIntegrationWindowsStoreDesc": { "message": "Δυστυχώς η ενσωμάτωση του περιηγητή, δεν υποστηρίζεται προς το παρόν στην έκδοση Windows Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json index 63e0cf96742..16af69361c6 100644 --- a/apps/desktop/src/locales/en_GB/messages.json +++ b/apps/desktop/src/locales/en_GB/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json index 832025c8c0e..c6f1253bb59 100644 --- a/apps/desktop/src/locales/en_IN/messages.json +++ b/apps/desktop/src/locales/en_IN/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Windows Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json index a3a8643a8f6..28a9f3b8bce 100644 --- a/apps/desktop/src/locales/eo/messages.json +++ b/apps/desktop/src/locales/eo/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json index 01163c7ad29..9966fa1064c 100644 --- a/apps/desktop/src/locales/es/messages.json +++ b/apps/desktop/src/locales/es/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Se ha producido un error mientras se habilitaba la integración del navegador." }, - "browserIntegrationMasOnlyDesc": { - "message": "Por desgracia la integración del navegador sólo está soportada por ahora en la versión de la Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Lamentablemente, la integración del navegador no está actualmente soportada en la versión de Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json index 25489871b3b..d85c52bb763 100644 --- a/apps/desktop/src/locales/et/messages.json +++ b/apps/desktop/src/locales/et/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Midagi läks valesti brauseriga ühendamisel." }, - "browserIntegrationMasOnlyDesc": { - "message": "Paraku on brauseri integratsioon hetkel toetatud ainult Mac App Store'i versioonis." - }, "browserIntegrationWindowsStoreDesc": { "message": "Paraku ei ole brauseri integratsioon hetkel Microsoft Store versioonis toetatud." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json index c1007d7d71c..36401df0078 100644 --- a/apps/desktop/src/locales/eu/messages.json +++ b/apps/desktop/src/locales/eu/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Zoritxarrez, Mac App Storeren bertsioan soilik onartzen da oraingoz nabigatzailearen integrazioa." - }, "browserIntegrationWindowsStoreDesc": { "message": "Zoritxarrez, nabigatzailearen integrazioa ez da onartzen Windows Storen bertsioan." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json index eb62c711628..caa241eb036 100644 --- a/apps/desktop/src/locales/fa/messages.json +++ b/apps/desktop/src/locales/fa/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "خطایی هنگام فعال‌سازی یکپارچه سازی مرورگر رخ داده است." }, - "browserIntegrationMasOnlyDesc": { - "message": "متأسفانه در حال حاضر ادغام مرورگر فقط در نسخه Mac App Store پشتیبانی می‌شود." - }, "browserIntegrationWindowsStoreDesc": { "message": "متأسفانه در حال حاضر ادغام مرورگر در نسخه فروشگاه ویندوز پشتیبانی نمی‌شود." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json index 06f0338e1f3..e2952659d03 100644 --- a/apps/desktop/src/locales/fi/messages.json +++ b/apps/desktop/src/locales/fi/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Otettaessa selainintegraatiota käyttöön tapahtui virhe." }, - "browserIntegrationMasOnlyDesc": { - "message": "Valitettavasti selainintegraatiota tuetaan toistaiseksi vain Mac App Store -versiossa." - }, "browserIntegrationWindowsStoreDesc": { "message": "Valitettavasti selainintegraatiota ei toistaiseksi tueta Microsoft Store -versiossa." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json index 6a32df33ecb..6eaa5577807 100644 --- a/apps/desktop/src/locales/fil/messages.json +++ b/apps/desktop/src/locales/fil/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Sa kasamaang palad ang pagsasama ng browser ay suportado lamang sa bersyon ng Mac App Store para sa ngayon." - }, "browserIntegrationWindowsStoreDesc": { "message": "Sa kasamaang palad ang pagsasama ng browser ay kasalukuyang hindi suportado sa bersyon ng Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index 0cd10d0582c..acd5037bb6d 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -283,7 +283,7 @@ "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." }, "contactCSToAvoidDataLossPart1": { - "message": "Contacter le service clientèle", + "message": "Contacter succès client", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Une erreur s'est produite lors de l'action de l'intégration du navigateur." }, - "browserIntegrationMasOnlyDesc": { - "message": "Malheureusement l'intégration avec le navigateur est uniquement supportée dans la version Mac App Store pour le moment." - }, "browserIntegrationWindowsStoreDesc": { "message": "Malheureusement l'intégration avec le navigateur n'est pas supportée dans la version Windows Store pour le moment." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json index 70d4c7cb494..d607bb8d097 100644 --- a/apps/desktop/src/locales/gl/messages.json +++ b/apps/desktop/src/locales/gl/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index abe445f83d5..87fac938a34 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "אירעה שגיאה בעת אפשור שילוב דפדפן." }, - "browserIntegrationMasOnlyDesc": { - "message": "למרבה הצער שילוב דפדפן נתמך רק בגרסת Mac App Store לעת עתה." - }, "browserIntegrationWindowsStoreDesc": { "message": "למרבה הצער שילוב דפדפן אינו נתמך כרגע בגרסת ה־Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json index 84676c4d941..2ab323eedc9 100644 --- a/apps/desktop/src/locales/hi/messages.json +++ b/apps/desktop/src/locales/hi/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json index d1c2ba68779..0f7a8185118 100644 --- a/apps/desktop/src/locales/hr/messages.json +++ b/apps/desktop/src/locales/hr/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Pogreška prillikom integracije s preglednikom." }, - "browserIntegrationMasOnlyDesc": { - "message": "Nažalost, za sada je integracija s preglednikom podržana samo u Mac App Store verziji aplikacije." - }, "browserIntegrationWindowsStoreDesc": { "message": "Nažalost, integracija s preglednikom trenutno nije podržana u Windows Store verziji aplikacije." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": " Nadogradi na Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Radnja nakon isteka" + }, + "sessionTimeoutHeader": { + "message": "Istek sesije" } } diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json index 9d296a7d2cc..9a6dd787f8c 100644 --- a/apps/desktop/src/locales/hu/messages.json +++ b/apps/desktop/src/locales/hu/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Hiba történt a böngésző integrációjának engedélyezése közben." }, - "browserIntegrationMasOnlyDesc": { - "message": "Sajnos a böngésző integrációt egyelőre csak a Mac App Store verzió támogatja." - }, "browserIntegrationWindowsStoreDesc": { "message": "A böngésző integrációt egyelőre csak a Windows Store verzió támogatja." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Áttérés Prémium csomagra" + }, + "sessionTimeoutSettingsAction": { + "message": "Időkifutási művelet" + }, + "sessionTimeoutHeader": { + "message": "Munkamenet időkifutás" } } diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json index 03da4bbd030..188ee153da1 100644 --- a/apps/desktop/src/locales/id/messages.json +++ b/apps/desktop/src/locales/id/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Sayangnya integrasi browser hanya didukung di versi Mac App Store untuk saat ini." - }, "browserIntegrationWindowsStoreDesc": { "message": "Sayangnya integrasi browser saat ini tidak didukung di versi Windows Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json index 4881d96b44a..1656a301b42 100644 --- a/apps/desktop/src/locales/it/messages.json +++ b/apps/desktop/src/locales/it/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Si è verificato un errore durante l'attivazione dell'integrazione del browser." }, - "browserIntegrationMasOnlyDesc": { - "message": "Purtroppo l'integrazione del browser è supportata solo nella versione nell'App Store per ora." - }, "browserIntegrationWindowsStoreDesc": { "message": "Purtroppo l'integrazione del browser non è supportata nella versione del Microsoft Store per ora." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json index 1dfb5a42ead..ca50828b12c 100644 --- a/apps/desktop/src/locales/ja/messages.json +++ b/apps/desktop/src/locales/ja/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "ブラウザー統合の有効化中にエラーが発生しました。" }, - "browserIntegrationMasOnlyDesc": { - "message": "残念ながら、ブラウザ統合は、Mac App Storeのバージョンでのみサポートされています。" - }, "browserIntegrationWindowsStoreDesc": { "message": "残念ながらお使いの Microsoft Store のバージョンではブラウザの統合に対応していません。" }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json index edaa68e7302..9337286d3fd 100644 --- a/apps/desktop/src/locales/ka/messages.json +++ b/apps/desktop/src/locales/ka/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json index 70d4c7cb494..d607bb8d097 100644 --- a/apps/desktop/src/locales/km/messages.json +++ b/apps/desktop/src/locales/km/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json index b880c845f4f..d1375efee8c 100644 --- a/apps/desktop/src/locales/kn/messages.json +++ b/apps/desktop/src/locales/kn/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "ದುರದೃಷ್ಟವಶಾತ್ ಬ್ರೌಸರ್ ಏಕೀಕರಣವನ್ನು ಇದೀಗ ಮ್ಯಾಕ್ ಆಪ್ ಸ್ಟೋರ್ ಆವೃತ್ತಿಯಲ್ಲಿ ಮಾತ್ರ ಬೆಂಬಲಿಸಲಾಗುತ್ತದೆ." - }, "browserIntegrationWindowsStoreDesc": { "message": "ದುರದೃಷ್ಟವಶಾತ್ ವಿಂಡೋಸ್ ಸ್ಟೋರ್ ಆವೃತ್ತಿಯಲ್ಲಿ ಬ್ರೌಸರ್ ಏಕೀಕರಣವನ್ನು ಪ್ರಸ್ತುತ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json index 7ef1645febf..2e40b8d7f23 100644 --- a/apps/desktop/src/locales/ko/messages.json +++ b/apps/desktop/src/locales/ko/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "브라우저와 연결은 현재 Mac App Store 버전에서만 지원됩니다." - }, "browserIntegrationWindowsStoreDesc": { "message": "현재 Microsoft Store 버전에서는 브라우저와 연결이 지원되지 않습니다." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json index 471bde7b410..16f328d6240 100644 --- a/apps/desktop/src/locales/lt/messages.json +++ b/apps/desktop/src/locales/lt/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Deja, bet naršyklės integravimas kol kas palaikomas tik Mac App Store versijoje." - }, "browserIntegrationWindowsStoreDesc": { "message": "Deja, bet naršyklės integravimas nepalaikomas Microsoft Store versijoje." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json index cc6cad0fd40..7800a4e9024 100644 --- a/apps/desktop/src/locales/lv/messages.json +++ b/apps/desktop/src/locales/lv/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Atgadījās kļūda pārlūka saistīšanas iespējošanas laikā." }, - "browserIntegrationMasOnlyDesc": { - "message": "Diemžēl sasaistīšāna ar pārlūku pagaidām ir nodrošināta tikai Mac App Store laidienā." - }, "browserIntegrationWindowsStoreDesc": { "message": "Diemžēl sasaistīšana ar pārlūku pagaidām nav nodrošināta Windows veikala laidienā." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Uzlabot uz Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Noildzes darbība" + }, + "sessionTimeoutHeader": { + "message": "Sesijas noildze" } } diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json index a67fa99079b..29e3cefee0c 100644 --- a/apps/desktop/src/locales/me/messages.json +++ b/apps/desktop/src/locales/me/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json index 4eee4cb0c0d..662ce9a1fc6 100644 --- a/apps/desktop/src/locales/ml/messages.json +++ b/apps/desktop/src/locales/ml/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json index 70d4c7cb494..d607bb8d097 100644 --- a/apps/desktop/src/locales/mr/messages.json +++ b/apps/desktop/src/locales/mr/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json index 047f272b564..bcbd26cede3 100644 --- a/apps/desktop/src/locales/my/messages.json +++ b/apps/desktop/src/locales/my/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json index 9bede02bfcf..42fb6d479c0 100644 --- a/apps/desktop/src/locales/nb/messages.json +++ b/apps/desktop/src/locales/nb/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Nettleserintegrasjon støttes dessverre bare i Mac App Store-versjonen for øyeblikket." - }, "browserIntegrationWindowsStoreDesc": { "message": "Nettleserintegrasjon er for øyeblikket dessverre ikke støttet i Windows Store-versjonen." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json index 093896ca17a..cce8f6a2ba5 100644 --- a/apps/desktop/src/locales/ne/messages.json +++ b/apps/desktop/src/locales/ne/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json index de0820ac5ed..82b51b018c5 100644 --- a/apps/desktop/src/locales/nl/messages.json +++ b/apps/desktop/src/locales/nl/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Er is iets misgegaan bij het tijdens het inschakelen van de browserintegratie." }, - "browserIntegrationMasOnlyDesc": { - "message": "Helaas wordt browserintegratie momenteel alleen ondersteund in de Mac App Store-versie." - }, "browserIntegrationWindowsStoreDesc": { "message": "Helaas wordt browserintegratie momenteel niet ondersteund in de Windows Store-versie." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Opwaarderen naar Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Time-out actie" + }, + "sessionTimeoutHeader": { + "message": "Sessietime-out" } } diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json index c582356f115..08567979e8b 100644 --- a/apps/desktop/src/locales/nn/messages.json +++ b/apps/desktop/src/locales/nn/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json index 89489e1db87..4ca05acaac5 100644 --- a/apps/desktop/src/locales/or/messages.json +++ b/apps/desktop/src/locales/or/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json index b9eb4b6232b..c05e7f05cb1 100644 --- a/apps/desktop/src/locales/pl/messages.json +++ b/apps/desktop/src/locales/pl/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Wystąpił błąd podczas włączania połączenia z przeglądarką." }, - "browserIntegrationMasOnlyDesc": { - "message": "Połączenie z przeglądarką jest obsługiwane tylko z wersją aplikacji ze sklepu Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Połączenie z przeglądarką nie jest obecnie obsługiwane w aplikacji w wersji Windows Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json index 9749d280516..7871ac72533 100644 --- a/apps/desktop/src/locales/pt_BR/messages.json +++ b/apps/desktop/src/locales/pt_BR/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Você não tem permissão para editar este item" }, "welcomeBack": { "message": "Boas-vindas de volta" @@ -283,7 +283,7 @@ "message": "O Bitwarden não pôde descriptografar o(s) item(ns) listados abaixo do cofre." }, "contactCSToAvoidDataLossPart1": { - "message": "Contate o sucesso do consumidor", + "message": "Contate o costumer success", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -473,7 +473,7 @@ "message": "Use caixas de seleção se gostaria de preencher automaticamente a caixa de seleção de um formulário, como um lembrar e-mail" }, "linkedHelpText": { - "message": "Use um campo vinculado quando você estiver experienciando problemas de preenchimento automático em um site específico." + "message": "Use um campo vinculado quando você estiver experienciando problemas com o preenchimento automático em um site específico." }, "linkedLabelHelpText": { "message": "Digite o ID html, nome, aria-label, ou placeholder do campo." @@ -1635,7 +1635,7 @@ "message": "Copiado com sucesso" }, "errorRefreshingAccessToken": { - "message": "Erro ao acessar token de recarregamento" + "message": "Erro de atualização do token de acesso" }, "errorRefreshingAccessTokenDesc": { "message": "Nenhum token de recarregamento ou chave de API foi encontrado. Tente desconectar-se e conectar-se novamente." @@ -1749,10 +1749,10 @@ "message": "Use a chave de criptografia da sua conta, derivada do nome de usuário e senha principal da sua conta, para criptografar a exportação e restringir a importação para apenas a conta atual do Bitwarden." }, "passwordProtected": { - "message": "Protegido por senha" + "message": "Protegida por senha" }, "passwordProtectedOptionDescription": { - "message": "Defina uma senha de arquivo para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografia." + "message": "Configure uma senha de arquivo para criptografar a exportação e importá-la para qualquer conta do Bitwarden usando a senha para descriptografá-la." }, "exportTypeHeading": { "message": "Tipo da exportação" @@ -1761,10 +1761,10 @@ "message": "Restrita à conta" }, "restrictCardTypeImport": { - "message": "Não é possível importar tipos de item de cartão" + "message": "Não é possível importar itens do tipo de cartão" }, "restrictCardTypeImportDesc": { - "message": "Uma política definida por 1 ou mais organizações impedem que você importe cartões em seus cofres." + "message": "Uma política configurada por uma ou mais organizações impedem que você importe cartões em seus cofres." }, "filePasswordAndConfirmFilePasswordDoNotMatch": { "message": "\"Senha do arquivo\" e \"Confirmar senha do arquivo\" não correspondem." @@ -1780,7 +1780,7 @@ "message": "Confirmar exportação do cofre" }, "exportWarningDesc": { - "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Exclua o arquivo imediatamente após terminar de usá-lo." + "message": "Esta exportação contém os dados do seu cofre em um formato não criptografado. Você não deve armazenar ou enviar o arquivo exportado por canais inseguros (como e-mail). Apague o arquivo imediatamente após terminar de usá-lo." }, "encExportKeyWarningDesc": { "message": "Esta exportação criptografa seus dados usando a chave de criptografia da sua conta. Se você rotacionar a chave de criptografia da sua conta, você deve exportar novamente, já que você não será capaz de descriptografar este arquivo de exportação." @@ -1789,7 +1789,7 @@ "message": "As chaves de criptografia de conta são únicas para cada conta de usuário do Bitwarden, então você não pode importar uma exportação criptografada para uma conta diferente." }, "noOrganizationsList": { - "message": "Você não pertence a nenhuma organização. Organizações permitem-lhe compartilhar itens em segurança com outros usuários." + "message": "Você não faz parte de uma organização. Organizações permitem-lhe compartilhar itens com segurança com outros usuários." }, "noCollectionsInList": { "message": "Não há coleções para listar." @@ -1798,7 +1798,7 @@ "message": "Propriedade" }, "whoOwnsThisItem": { - "message": "Quem possui este item?" + "message": "Quem é o proprietário deste item?" }, "strong": { "message": "Forte", @@ -1816,17 +1816,17 @@ "message": "Senha principal fraca" }, "weakMasterPasswordDesc": { - "message": "A senha principal que você selecionou está fraca. Você deve usar uma senha principal forte (ou uma frase-passe) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" + "message": "A senha principal que você escolheu é fraca. Você deve usar uma senha principal forte (ou uma frase secreta) para proteger a sua conta Bitwarden adequadamente. Tem certeza que deseja usar esta senha principal?" }, "pin": { "message": "PIN", "description": "PIN code. Ex. The short code (often numeric) that you use to unlock a device." }, "unlockWithPin": { - "message": "Desbloquear com o PIN" + "message": "Desbloquear com PIN" }, "setYourPinCode": { - "message": "Defina o seu código PIN para desbloquear o Bitwarden. Suas configurações de PIN serão redefinidas se alguma vez você encerrar completamente toda a sessão do aplicativo." + "message": "Configure o seu código PIN para desbloquear o Bitwarden. Suas configurações de PIN serão redefinidas se você desconectar-se totalmente do aplicativo." }, "pinRequired": { "message": "O código PIN é necessário." @@ -1841,7 +1841,7 @@ "message": "Desbloquear com o Windows Hello" }, "unlockWithPolkit": { - "message": "Desbloquear com autenticação de sistema" + "message": "Desbloquear com a autenticação do sistema" }, "windowsHelloConsentMessage": { "message": "Verifique para o Bitwarden." @@ -1850,16 +1850,16 @@ "message": "Desbloquear com o Touch ID" }, "additionalTouchIdSettings": { - "message": "Configurações adicionais de Touch ID" + "message": "Configurações adicionais do Touch ID" }, "touchIdConsentMessage": { "message": "desbloquear o seu cofre" }, "autoPromptTouchId": { - "message": "Pedir pelo Touch ID ao iniciar" + "message": "Pedir o Touch ID ao abrir" }, "lockWithMasterPassOnRestart1": { - "message": "Bloquear com senha principal ao reiniciar" + "message": "Bloquear com a senha principal ao reiniciar" }, "requireMasterPasswordOrPinOnAppRestart": { "message": "Exigir senha principal ou PIN ao reiniciar o app" @@ -1886,7 +1886,7 @@ "message": "Conta apagada" }, "accountDeletedDesc": { - "message": "A sua conta foi fechada e todos os dados associados foram excluídos." + "message": "A sua conta foi fechada e todos os dados associados foram apagados." }, "preferences": { "message": "Preferências" @@ -1898,7 +1898,7 @@ "message": "Sempre mostrar um ícone na barra de menu." }, "hideToMenuBar": { - "message": "Ocultar para a barra de menu" + "message": "Esconder na barra de menu" }, "selectOneCollection": { "message": "Você deve selecionar pelo menos uma coleção." @@ -1910,10 +1910,10 @@ "message": "Restaurar" }, "premiumManageAlertAppStore": { - "message": "Você pode gerenciar sua assinatura da App Store. Você quer visitar a App Store agora?" + "message": "Você pode gerenciar sua assinatura pela App Store. Você quer visitar a App Store agora?" }, "legal": { - "message": "Aspectos Legais", + "message": "Jurídico", "description": "Noun. As in 'legal documents', like our terms of service and privacy policy." }, "termsOfService": { @@ -1923,7 +1923,7 @@ "message": "Política de Privacidade" }, "unsavedChangesConfirmation": { - "message": "Você tem certeza que deseja sair? Se sair agora, as suas informações atuais não serão salvas." + "message": "Tem certeza que quer sair? Se sair agora, as suas informações atuais não serão salvas." }, "unsavedChangesTitle": { "message": "Alterações não salvas" @@ -1932,7 +1932,7 @@ "message": "Clonar" }, "passwordGeneratorPolicyInEffect": { - "message": "Uma ou mais políticas da organização estão afetando as suas configurações do gerador." + "message": "Uma ou mais políticas da organização estão afetando as configurações do seu gerador." }, "vaultTimeoutAction": { "message": "Ação do tempo limite do cofre" @@ -1961,7 +1961,7 @@ "message": "Apagar item para sempre" }, "permanentlyDeleteItemConfirmation": { - "message": "Você tem certeza que deseja excluir permanentemente esse item?" + "message": "Tem certeza que quer apagar este item para sempre?" }, "permanentlyDeletedItem": { "message": "Item apagado para sempre" @@ -1985,11 +1985,11 @@ "message": "Configurar senha principal" }, "orgPermissionsUpdatedMustSetPassword": { - "message": "As permissões da sua organização foram atualizadas, exigindo que você defina uma senha principal.", + "message": "As permissões da sua organização foram atualizadas, exigindo que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "orgRequiresYouToSetPassword": { - "message": "Sua organização requer que você defina uma senha principal.", + "message": "Sua organização requer que você configure uma senha principal.", "description": "Used as a card title description on the set password page to explain why the user is there" }, "cardMetrics": { @@ -2023,10 +2023,10 @@ "message": "Torne a verificação em 2 etapas mais simples" }, "totpHelper": { - "message": "Bitwarden pode armazenar e preencher códigos de verificação de 2 etapas. Copie e cole a chave nesse campo." + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de 2 etapas. Copie e cole a chave nesse campo." }, "totpHelperWithCapture": { - "message": "Bitwarden pode armazenar e preencher códigos de verificação de 2 etapas. Selecione o ícone da câmera e tire uma captura de tela do código QR de autenticação desse site ou copie e cole a chave nesse campo." + "message": "O Bitwarden pode armazenar e preencher códigos de verificação de 2 etapas. Selecione o ícone da câmera e tire uma captura de tela do código QR do autenticador nesse site ou copie e cole a chave nesse campo." }, "premium": { "message": "Premium", @@ -2048,10 +2048,10 @@ } }, "cardExpiredTitle": { - "message": "Cartão expirado" + "message": "Cartão vencido" }, "cardExpiredMessage": { - "message": "Se você fez uma renovação recente, atualize as informações do cartão" + "message": "Se você o renovou, atualize as informações do cartão" }, "verificationRequired": { "message": "Verificação necessária", @@ -2133,7 +2133,7 @@ "message": "Permitir integração com o navegador" }, "enableBrowserIntegrationDesc1": { - "message": "Usado para permitir desbloqueio biométrico em navegadores que não são o Safari." + "message": "Usado para permitir o desbloqueio biométrico em navegadores que não são o Safari." }, "enableDuckDuckGoBrowserIntegration": { "message": "Permitir integração com o navegador DuckDuckGo" @@ -2145,16 +2145,13 @@ "message": "Integração com o navegador não suportada" }, "browserIntegrationErrorTitle": { - "message": "Erro ao ativar a integração do navegador" + "message": "Erro ao ativar a integração com o navegador" }, "browserIntegrationErrorDesc": { - "message": "Ocorreu um erro ao ativar a integração do navegador." - }, - "browserIntegrationMasOnlyDesc": { - "message": "Infelizmente, por ora, a integração do navegador só é suportada na versão da Mac App Store." + "message": "Ocorreu um erro ao ativar a integração com o navegador." }, "browserIntegrationWindowsStoreDesc": { - "message": "Infelizmente, a integração do navegador não é suportada na versão da Microsoft Store." + "message": "Infelizmente, a integração com o navegador não é suportada na versão da Microsoft Store no momento." }, "browserIntegrationLinuxDesc": { "message": "Infelizmente, a integração do navegador não é suportada na versão linux no momento." @@ -2163,22 +2160,22 @@ "message": "Exigir verificação para integração com o navegador" }, "enableBrowserIntegrationFingerprintDesc": { - "message": "Adicione uma camada adicional de segurança, exigindo validação de frase biométrica ao estabelecer uma ligação entre o computador e o navegador. Quando ativado, isto requer intervenção do usuário e verificação cada vez que uma conexão é estabelecida." + "message": "Adicione uma camada adicional de segurança, exigindo a validação da frase biométrica ao estabelecer uma ligação entre o computador e o navegador. Requer intervenção do usuário e verificação cada vez que uma conexão é estabelecida." }, "enableHardwareAcceleration": { - "message": "Utilizar aceleração de hardware" + "message": "Usar aceleração de hardware" }, "enableHardwareAccelerationDesc": { - "message": "Por padrão esta configuração está ativada. Desligue apenas se tiver problemas gráficos. Reiniciar é necessário." + "message": "Por padrão esta configuração está ativada. Desative apenas se tiver problemas gráficos. Reiniciar é necessário." }, "approve": { "message": "Aprovar" }, "verifyBrowserTitle": { - "message": "Verificar conexão do navegador" + "message": "Verifique a conexão com o navegador" }, "verifyBrowserDesc": { - "message": "Por favor, certifique-se que a impressão digital mostrada é idêntica à impressão digital exibida na extensão do navegador." + "message": "Certifique-se que a frase biométrica mostrada é idêntica à exibida na extensão do navegador." }, "verifyNativeMessagingConnectionTitle": { "message": "$APPID$ quer se conectar ao Bitwarden", @@ -2190,7 +2187,7 @@ } }, "verifyNativeMessagingConnectionDesc": { - "message": "Gostaria de aprovar este pedido?" + "message": "Gostaria de aprovar esta solicitação?" }, "verifyNativeMessagingConnectionWarning": { "message": "Se não iniciou esta solicitação, não a aprove." @@ -2214,19 +2211,19 @@ "message": "Sua senha nova não pode ser a mesma que a sua atual." }, "hintEqualsPassword": { - "message": "Sua dica de senha não pode ser o mesmo que sua senha." + "message": "A dica da sua senha não pode ser a mesma que a sua senha." }, "personalOwnershipPolicyInEffect": { "message": "Uma política de organização está afetando suas opções de propriedade." }, "personalOwnershipPolicyInEffectImports": { - "message": "Uma política da organização bloqueou a importação de itens em seu cofre pessoal." + "message": "Uma política da organização bloqueou a importação de itens em seu cofre individual." }, "personalDetails": { "message": "Detalhes pessoais" }, "identification": { - "message": "Identificação" + "message": "Identidade" }, "contactInfo": { "message": "Informações de contato" @@ -2259,14 +2256,14 @@ "message": "Data de apagamento" }, "deletionDateDesc": { - "message": "O Send será eliminado permanentemente na data e hora especificadas.", + "message": "O Send será apagado para sempre no horário especificado.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "expirationDate": { "message": "Data de validade" }, "expirationDateDesc": { - "message": "Se definido, o acesso a este Send expirará na data e hora especificadas.", + "message": "Se configurado, o acesso a este Send acabará no horário especificado.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "maxAccessCount": { @@ -2274,7 +2271,7 @@ "description": "This text will be displayed after a Send has been accessed the maximum amount of times." }, "maxAccessCountDesc": { - "message": "Se atribuído, usuários não poderão mais acessar este Send assim que o número máximo de acessos for atingido.", + "message": "Se configurado, usuários não poderão mais acessar este Send assim que o número máximo de acessos for atingido.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "currentAccessCount": { @@ -2285,11 +2282,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendPasswordDesc": { - "message": "Opcionalmente exigir uma senha para os usuários acessarem este Send.", + "message": "Opcionalmente exija uma senha para os usuários acessarem este Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendNotesDesc": { - "message": "Notas privadas sobre este Send.", + "message": "Anotações privadas sobre este Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendLink": { @@ -2349,7 +2346,7 @@ "message": "Personalizado" }, "deleteSendConfirmation": { - "message": "Você tem certeza que deseja excluir este Send?", + "message": "Tem certeza que quer apagar este Send?", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copySendLinkToClipboard": { @@ -2364,7 +2361,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { - "message": "Devido a uma política corporativa, você só pode excluir um Send existente.", + "message": "Devido a uma política corporativa, você só pode apagar um Send existente.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "copyLink": { @@ -2386,10 +2383,10 @@ "message": "Número máximo de acessos atingido" }, "expired": { - "message": "Expirado" + "message": "Vencido" }, "pendingDeletion": { - "message": "Exclusão pendente" + "message": "Apagamento pendente" }, "webAuthnAuthenticate": { "message": "Autenticar WebAuthn" @@ -2416,16 +2413,16 @@ "message": "Você precisa verificar o seu e-mail para usar este recurso." }, "passwordPrompt": { - "message": "Solicitação nova de senha principal" + "message": "Resolicitar senha principal" }, "passwordConfirmation": { "message": "Confirmação de senha principal" }, "passwordConfirmationDesc": { - "message": "Esta ação está protegida. Para continuar, por favor, reinsira a sua senha principal para verificar sua identidade." + "message": "Esta ação está protegida. Redigite a sua senha principal para verificar sua identidade." }, "masterPasswordSuccessfullySet": { - "message": "Senha principal definida com sucesso" + "message": "Senha principal configurada com sucesso" }, "updatedMasterPassword": { "message": "Senha principal atualizada" @@ -2434,31 +2431,31 @@ "message": "Atualizar senha principal" }, "updateMasterPasswordWarning": { - "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você entre novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "Sua senha principal foi alterada recentemente por um administrador de sua organização. Para acessar o cofre, você precisa atualizá-la agora. O processo desconectará você da sessão atual, exigindo que você conecte-se novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "updateWeakMasterPasswordWarning": { - "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você inicie a sessão novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." + "message": "A sua senha principal não atende a uma ou mais das políticas da sua organização. Para acessar o cofre, você deve atualizar a sua senha principal agora. O processo desconectará você da sessão atual, exigindo que você se conecte novamente. Sessões ativas em outros dispositivos podem continuar ativas por até uma hora." }, "changePasswordWarning": { - "message": "Após mudar a sua senha, será necessário entrar novamente com a sua nova senha. Sessões ativas em outros dispositivos serão encerradas em até uma hora." + "message": "Após mudar a sua senha, será necessário conectar-se novamente com a sua nova senha. Sessões ativas em outros dispositivos serão desconectadas em até uma hora." }, "accountRecoveryUpdateMasterPasswordSubtitle": { - "message": "Mude a sua senha principal para completar a recuperação de conta." + "message": "Altere a sua senha principal para concluir a recuperação da conta." }, "updateMasterPasswordSubtitle": { "message": "Sua senha principal não corresponde aos requisitos da organização. Mude a sua senha principal para continuar." }, "tdeDisabledMasterPasswordRequired": { - "message": "Sua organização desativou a criptografia confiável do dispositivo. Defina uma senha principal para acessar o seu cofre." + "message": "Sua organização desativou a criptografia de dispositivo confiado. Configure uma senha principal para acessar o seu cofre." }, "tryAgain": { - "message": "Repetir" + "message": "Tentar novamente" }, "verificationRequiredForActionSetPinToContinue": { - "message": "Verificação necessária para esta ação. Defina um PIN para continuar." + "message": "Verificação necessária para esta ação. Configure um PIN para continuar." }, "setPin": { - "message": "Definir PIN" + "message": "Configurar PIN" }, "verifyWithBiometrics": { "message": "Verificar com biometria" @@ -2467,13 +2464,13 @@ "message": "Aguardando confirmação" }, "couldNotCompleteBiometrics": { - "message": "Não foi possível completar a biometria." + "message": "Não foi possível concluir a biometria." }, "needADifferentMethod": { "message": "Precisa de um método diferente?" }, "useMasterPassword": { - "message": "Usar a senha principal" + "message": "Usar senha principal" }, "usePin": { "message": "Usar PIN" @@ -2482,7 +2479,7 @@ "message": "Usar biometria" }, "enterVerificationCodeSentToEmail": { - "message": "Digite o código de verificação que foi enviado para o seu e-mail." + "message": "Digite o código de verificação enviado para o seu e-mail." }, "resendCode": { "message": "Reenviar código" @@ -2507,7 +2504,7 @@ } }, "vaultTimeoutPolicyWithActionInEffect": { - "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. O tempo limite máximo permitido para cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre é definida como $ACTION$.", + "message": "As políticas da sua organização estão afetando o tempo limite do seu cofre. O máximo permitido do tempo limite do cofre é $HOURS$ hora(s) e $MINUTES$ minuto(s). A ação de tempo limite do seu cofre está configurada para $ACTION$.", "placeholders": { "hours": { "content": "$1", @@ -2524,7 +2521,7 @@ } }, "vaultTimeoutActionPolicyInEffect": { - "message": "As políticas da sua organização definiram a ação do tempo limite do seu cofre para $ACTION$.", + "message": "As políticas da sua organização configuraram a ação do tempo limite do seu cofre para $ACTION$.", "placeholders": { "action": { "content": "$1", @@ -2536,10 +2533,10 @@ "message": "O tempo limite do seu cofre excede as restrições definidas por sua organização." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Requisitos de políticas corporativas foram adicionadas as suas opções de tempo limite" + "message": "Os requisitos das políticas corporativas foram aplicados às suas opções de tempo limite" }, "vaultTimeoutPolicyInEffect": { - "message": "As políticas da sua organização definiram o seu tempo limite máximo permitido no cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", + "message": "As políticas da sua organização configuraram o seu máximo permitido do tempo limite do cofre para $HOURS$ hora(s) e $MINUTES$ minuto(s).", "placeholders": { "hours": { "content": "$1", @@ -2565,7 +2562,7 @@ } }, "vaultCustomTimeoutMinimum": { - "message": "O tempo limite personalizado mínimo é de 1 minuto." + "message": "O mínimo do tempo limite personalizado é de 1 minuto." }, "inviteAccepted": { "message": "Convite aceito" @@ -2580,7 +2577,7 @@ "message": "Exportação de cofre removida" }, "personalVaultExportPolicyInEffect": { - "message": "Uma ou mais políticas da organização impdem que você exporte seu cofre pessoal." + "message": "Uma ou mais políticas da organização impedem que você exporte seu cofre pessoal." }, "addAccount": { "message": "Adicionar conta" @@ -2592,13 +2589,13 @@ "message": "Senha principal removida" }, "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Por favor, confirme o domínio abaixo com o administrador da sua organização." + "message": "Uma senha principal não é mais necessária para membros da seguinte organização. Confirme o domínio abaixo com o administrador da sua organização." }, "organizationName": { "message": "Nome da organização" }, "keyConnectorDomain": { - "message": "Domínio do Conector de Chave" + "message": "Domínio do Key Connector" }, "leaveOrganization": { "message": "Sair da organização" @@ -2610,25 +2607,25 @@ "message": "Você saiu da organização." }, "ssoKeyConnectorError": { - "message": "Erro do conector de chave: certifique-se de que o conector de chave está disponível e funcionando corretamente." + "message": "Erro do Key Connector: certifique-se de que o Key Connector está disponível e funcionando corretamente." }, "lockAllVaults": { "message": "Bloquear todos os cofres" }, "accountLimitReached": { - "message": "Não mais do que 5 contas podem estar logadas ao mesmo tempo." + "message": "Não mais do que 5 contas podem estar conectadas ao mesmo tempo." }, "accountPreferences": { "message": "Preferências" }, "appPreferences": { - "message": "Configurações do Aplicativo (Todas as Contas)" + "message": "Configurações do aplicativo (todas as contas)" }, "accountSwitcherLimitReached": { - "message": "Limite de Contas atingido. Saia de uma conta para adicionar outra." + "message": "Limite de contas atingido. Desconecte uma conta para adicionar outra." }, "settingsTitle": { - "message": "Configurações do Aplicativo para $EMAIL$", + "message": "Configurações do aplicativo para $EMAIL$", "placeholders": { "email": { "content": "$1", @@ -2646,13 +2643,13 @@ "message": "Opções" }, "sessionTimeout": { - "message": "Sua sessão expirou. Volte e tente entrar novamente." + "message": "Sua sessão expirou. Volte e tente se conectar novamente." }, "exportingPersonalVaultTitle": { "message": "Exportando cofre individual" }, "exportingIndividualVaultDescription": { - "message": "Apenas os itens do cofre individual associados com $EMAIL$ serão exportados. Os itens do cofre da organização não serão incluídos. Apenas as informações dos itens do cofre serão exportadas e não incluirão anexos associados.", + "message": "Apenas os itens do cofre individual associados a $EMAIL$ serão exportados. Os itens do cofre de organizações não serão incluídos. Apenas as informações dos itens do cofre serão exportadas e não incluirão anexos associados.", "placeholders": { "email": { "content": "$1", @@ -2661,7 +2658,7 @@ } }, "exportingIndividualVaultWithAttachmentsDescription": { - "message": "Apenas os itens do cofre individual, incluindo anexos associados com $EMAIL$ serão exportados. Os itens do cofre da organização não serão incluídos", + "message": "Apenas os itens do cofre individual, incluindo anexos associados com $EMAIL$ serão exportados. Os itens do cofre de organizações não serão incluídos", "placeholders": { "email": { "content": "$1", @@ -2673,7 +2670,7 @@ "message": "Exportando cofre da organização" }, "exportingOrganizationVaultDesc": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre individual e itens de outras organizações não serão incluídos.", + "message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Itens de cofres individuais e de outras organizações não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -2691,7 +2688,7 @@ } }, "exportingOrganizationVaultFromAdminConsoleWithDataOwnershipDesc": { - "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Os itens da minhas coleções não serão incluídos.", + "message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. As coleções de itens não serão incluídos.", "placeholders": { "organization": { "content": "$1", @@ -2713,13 +2710,13 @@ "description": "Short for 'credential generator'." }, "whatWouldYouLikeToGenerate": { - "message": "O que você gostaria de gerar?" + "message": "O que gostaria de gerar?" }, "passwordType": { - "message": "Tipo de senha" + "message": "Tipo da senha" }, "regenerateUsername": { - "message": "Regenerar nome de usuário" + "message": "Regerar nome de usuário" }, "generateUsername": { "message": "Gerar nome de usuário" @@ -2740,7 +2737,7 @@ "message": "Senha gerada" }, "passphraseGenerated": { - "message": "Gerador de frase secreta" + "message": "Frase secreta gerada" }, "usernameGenerated": { "message": "Nome de usuário gerado" @@ -2796,19 +2793,19 @@ "message": "E-mail pega-tudo" }, "catchallEmailDesc": { - "message": "Use o catch-all configurado no seu domínio." + "message": "Use a caixa de entrada pega-tudo configurada no seu domínio." }, "useThisEmail": { "message": "Usar este e-mail" }, "useThisPassword": { - "message": "Use esta senha" + "message": "Usar esta senha" }, "useThisPassphrase": { - "message": "Use esta frase secreta" + "message": "Usar esta frase secreta" }, "useThisUsername": { - "message": "Use este nome de usuário" + "message": "Usar este nome de usuário" }, "random": { "message": "Aleatório" @@ -2826,13 +2823,13 @@ "message": "Todos os cofres" }, "searchOrganization": { - "message": "Pesquisar organização" + "message": "Buscar na organização" }, "searchMyVault": { - "message": "Pesquisar meu cofre" + "message": "Buscar no meu cofre" }, "forwardedEmail": { - "message": "Alias de Encaminhamento de E-mail" + "message": "Alias de encaminhamento de e-mail" }, "forwardedEmailDesc": { "message": "Gere um alias de e-mail com um serviço externo de encaminhamento." @@ -2846,7 +2843,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "Erro $SERVICENAME$: $ERRORMESSAGE$", + "message": "Erro do $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2874,7 +2871,7 @@ } }, "forwaderInvalidToken": { - "message": "Token de API $SERVICENAME$ inválido", + "message": "Token de API do $SERVICENAME$ inválido", "description": "Displayed when the user's API token is empty or rejected by the forwarding service.", "placeholders": { "servicename": { @@ -2884,7 +2881,7 @@ } }, "forwaderInvalidTokenWithMessage": { - "message": "Token de API $SERVICENAME$ inválido: $ERRORMESSAGE$", + "message": "Token de API da $SERVICENAME$ inválido: $ERRORMESSAGE$", "description": "Displayed when the user's API token is rejected by the forwarding service with an error message.", "placeholders": { "servicename": { @@ -2922,7 +2919,7 @@ } }, "forwarderNoAccountId": { - "message": "Não foi possível obter o ID da conta de e-mail mascarado $SERVICENAME$.", + "message": "Não é possível obter o ID da conta de e-mail mascarado do $SERVICENAME$.", "description": "Displayed when the forwarding service fails to return an account ID.", "placeholders": { "servicename": { @@ -2932,7 +2929,7 @@ } }, "forwarderNoDomain": { - "message": "Domínio $SERVICENAME$ inválido.", + "message": "Domínio inválido do $SERVICENAME$.", "description": "Displayed when the domain is empty or domain authorization failed at the forwarding service.", "placeholders": { "servicename": { @@ -2942,7 +2939,7 @@ } }, "forwarderNoUrl": { - "message": "URL $SERVICENAME$ inválido.", + "message": "URL inválido do $SERVICENAME$.", "description": "Displayed when the url of the forwarding service wasn't supplied.", "placeholders": { "servicename": { @@ -2952,7 +2949,7 @@ } }, "forwarderUnknownError": { - "message": "Ocorreu um erro $SERVICENAME$ desconhecido.", + "message": "Ocorreu um erro desconhecido do $SERVICENAME$.", "description": "Displayed when the forwarding service failed due to an unknown error.", "placeholders": { "servicename": { @@ -2972,7 +2969,7 @@ } }, "hostname": { - "message": "Servidor", + "message": "Nome do servidor", "description": "Part of a URL." }, "apiAccessToken": { @@ -2997,16 +2994,16 @@ "message": "Cofre" }, "loginWithMasterPassword": { - "message": "Entrar com senha principal" + "message": "Conectar-se com senha principal" }, "rememberEmail": { - "message": "Lembrar e-mail" + "message": "Lembrar do e-mail" }, "newAroundHere": { "message": "Novo por aqui?" }, "loggingInTo": { - "message": "Entrando em $DOMAIN$", + "message": "Conectando-se a $DOMAIN$", "placeholders": { "domain": { "content": "$1", @@ -3015,7 +3012,7 @@ } }, "logInWithAnotherDevice": { - "message": "Entrar com outro dispositivo" + "message": "Conectar-se com outro dispositivo" }, "loginInitiated": { "message": "Autenticação iniciada" @@ -3033,7 +3030,7 @@ "message": "Desbloqueie o Bitwarden no seu dispositivo ou no " }, "notificationSentDeviceAnchor": { - "message": "app web" + "message": "aplicativo web" }, "notificationSentDevicePart2": { "message": "Certifique-se de que a frase biométrica corresponde à frase abaixo antes de aprovar." @@ -3051,7 +3048,7 @@ "message": "Você será notificado assim que a solicitação for aprovada" }, "needAnotherOption": { - "message": "A entrada com dispositivo deve ser ativada nas configurações do aplicativo móvel do Bitwarden. Precisa de outra opção?" + "message": "A autenticação com dispositivo deve ser ativada nas configurações do aplicativo móvel do Bitwarden. Precisa de outra opção?" }, "viewAllLogInOptions": { "message": "Ver todas as opções de autenticação" @@ -3089,10 +3086,10 @@ } }, "youDeniedLoginAttemptFromAnotherDevice": { - "message": "Você negou uma tentativa de autenticação por outro dispositivo. Se foi você, tente entrar com o dispositivo novamente." + "message": "Você negou uma tentativa de autenticação por outro dispositivo. Se foi você, tente conectar-se com o dispositivo novamente." }, "webApp": { - "message": "App web" + "message": "Aplicativo web" }, "mobile": { "message": "Móvel", @@ -3120,7 +3117,7 @@ "message": "Solicitação de autenticação" }, "deviceType": { - "message": "Tipo de dispositivo" + "message": "Tipo do dispositivo" }, "ipAddress": { "message": "Endereço de IP" @@ -3183,7 +3180,7 @@ "message": "Nenhum e-mail?" }, "goBack": { - "message": "Voltar" + "message": "Volte" }, "toEditYourEmailAddress": { "message": "para editar o seu endereço de e-mail." @@ -3213,10 +3210,10 @@ "message": "Acessando" }, "accessTokenUnableToBeDecrypted": { - "message": "Você foi desconectado porque seu token de acesso não pôde ser descriptografado. Por favor, entre novamente para resolver esse problema." + "message": "Você foi desconectado porque seu token de acesso não pôde ser descriptografado. Conecte-se novamente para resolver esse problema." }, "refreshTokenSecureStorageRetrievalFailure": { - "message": "Você foi desconectado porque seu token de recarregamento não pôde ser recuperado. Por favor, entre novamente para resolver esse problema." + "message": "Você foi desconectado porque seu token de recarregamento não pôde ser recuperado. Conecte-se novamente para resolver esse problema." }, "masterPasswordHint": { "message": "A sua senha principal não pode ser recuperada se você esquecê-la!" @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Faça upgrade para o Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Ação do tempo limite" + }, + "sessionTimeoutHeader": { + "message": "Tempo limite da sessão" } } diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json index d19c0075873..de0427ddab0 100644 --- a/apps/desktop/src/locales/pt_PT/messages.json +++ b/apps/desktop/src/locales/pt_PT/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Não tem permissão para editar este item" }, "welcomeBack": { "message": "Bem-vindo de volta" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Ocorreu um erro ao ativar a integração do navegador." }, - "browserIntegrationMasOnlyDesc": { - "message": "Infelizmente, a integração do navegador só é suportada na versão da Mac App Store por enquanto." - }, "browserIntegrationWindowsStoreDesc": { "message": "Infelizmente, a integração do navegador não é atualmente suportada na versão da Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Atualizar para o Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Ação de tempo limite" + }, + "sessionTimeoutHeader": { + "message": "Tempo limite da sessão" } } diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json index fbb1d018b60..a72ce3547e9 100644 --- a/apps/desktop/src/locales/ro/messages.json +++ b/apps/desktop/src/locales/ro/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Din păcate, integrarea browserului este acceptată numai în versiunea Mac App Store pentru moment." - }, "browserIntegrationWindowsStoreDesc": { "message": "Din păcate, integrarea browserului nu este susținută în prezent în versiunea Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json index 0170bd57310..914bb603630 100644 --- a/apps/desktop/src/locales/ru/messages.json +++ b/apps/desktop/src/locales/ru/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Произошла ошибка при включении интеграции с браузером." }, - "browserIntegrationMasOnlyDesc": { - "message": "К сожалению, интеграция браузера пока поддерживается только в версии Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "К сожалению, интеграция браузера в версии для Microsoft Store в настоящее время не поддерживается." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Обновить до Премиум" + }, + "sessionTimeoutSettingsAction": { + "message": "Тайм-аут действия" + }, + "sessionTimeoutHeader": { + "message": "Тайм-аут сеанса" } } diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json index 4193867ead5..a83b2cbf536 100644 --- a/apps/desktop/src/locales/si/messages.json +++ b/apps/desktop/src/locales/si/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index 8fecc5de1b9..e5763d78b9c 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1856,7 +1856,7 @@ "message": "odomknúť svoj trezor" }, "autoPromptTouchId": { - "message": "Pri spustení požiadať o Touch ID" + "message": "Pri spustení aplikácie požiadať o Touch ID" }, "lockWithMasterPassOnRestart1": { "message": "Pri reštarte zamknúť hlavným heslom" @@ -1904,7 +1904,7 @@ "message": "Musíte vybrať aspoň jednu zbierku." }, "premiumUpdated": { - "message": "Povýšili ste na prémium." + "message": "Upgradovali ste na Prémium." }, "restore": { "message": "Obnoviť" @@ -1964,10 +1964,10 @@ "message": "Naozaj chcete natrvalo odstrániť túto položku?" }, "permanentlyDeletedItem": { - "message": "Položka natrvalo odstránená" + "message": "Položka bola natrvalo odstránená" }, "restoredItem": { - "message": "Obnovená položka" + "message": "Položka bola obnovená" }, "permanentlyDelete": { "message": "Natrvalo odstrániť" @@ -2139,7 +2139,7 @@ "message": "Povoliť integráciu prehliadača DuckDuckGo" }, "enableDuckDuckGoBrowserIntegrationDesc": { - "message": "Používajte svoj trezor Bitwarden pri prehliadaní pomocou DuckDuckGo." + "message": "Používajte svoj trezor v Bitwardene pri prehliadaní pomocou DuckDuckGo." }, "browserIntegrationUnsupportedTitle": { "message": "Integrácia v prehliadači nie je podporovaná" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Pri povoľovaní integrácie v prehliadači sa vyskytla chyba." }, - "browserIntegrationMasOnlyDesc": { - "message": "Bohužiaľ, integrácia v prehliadači je zatiaľ podporovaná iba vo verzii Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Bohužiaľ, integrácia v prehliadači zatiaľ nie je podporovaná, ak je aplikácia nainštalovaná cez Microsoft Store." }, @@ -2181,7 +2178,7 @@ "message": "Uistite sa, že zobrazený odtlačok prsta je identický s odtlačkom prsta zobrazeným v rozšírení prehliadača." }, "verifyNativeMessagingConnectionTitle": { - "message": "$APPID$ sa chce pripojiť k Bitwarden", + "message": "$APPID$ sa chce pripojiť k Bitwardenu", "placeholders": { "appid": { "content": "$1", @@ -2208,7 +2205,7 @@ "message": "Vzhľadom na spôsob inštalácie nebolo možné automaticky povoliť podporu biometrie. Chcete otvoriť dokumentáciu, ako to urobiť manuálne?" }, "personalOwnershipSubmitError": { - "message": "Z dôvodu podnikovej politiky máte obmedzené ukladanie položiek do osobného trezora. Zmeňte možnosť vlastníctvo na organizáciu a vyberte si z dostupných zbierok." + "message": "Z dôvodu podnikových pravidiel máte obmedzené ukladanie položiek do osobného trezora. Zmeňte možnosť vlastníctvo na organizáciu a vyberte si z dostupných zbierok." }, "yourNewPasswordCannotBeTheSameAsYourCurrentPassword": { "message": "Nové heslo nemôže byť rovnaké ako súčasné heslo." @@ -2305,15 +2302,15 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createdSend": { - "message": "Send vytvorený", + "message": "Send bol vytvorený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "editedSend": { - "message": "Send upravený", + "message": "Send bol upravený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Send odstránený", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "newPassword": { @@ -2324,7 +2321,7 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "createSend": { - "message": "Vytvoriť Send", + "message": "Nový Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendTextDesc": { @@ -2360,7 +2357,7 @@ "message": "Kopírovať odkaz na zdieľanie tohto Sendu do schránky počas ukladania." }, "sendDisabled": { - "message": "Funkcia Send zakázaná", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -2985,10 +2982,10 @@ "message": "Vyžaduje sa predplatné Prémium" }, "organizationIsDisabled": { - "message": "Organizácia je vypnutá." + "message": "Organizácia je pozastavená" }, "disabledOrganizationFilterError": { - "message": "K položkám vo vypnutej organizácii nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." + "message": "K položkám pozastavenej organizácii nie je možné pristupovať. Požiadajte o pomoc vlastníka organizácie." }, "neverLockWarning": { "message": "Ste si istí, že chcete použiť možnosť \"Nikdy\"? Táto predvoľba ukladá šifrovací kľúč od trezora priamo na zariadení. Ak použijete túto možnosť, mali by ste svoje zariadenie náležite zabezpečiť." @@ -3189,16 +3186,16 @@ "message": "na úpravu e-mailovej adresy." }, "exposedMasterPassword": { - "message": "Odhalené hlavné heslo" + "message": "Uniknuté hlavné heslo" }, "exposedMasterPasswordDesc": { - "message": "Nájdené heslo v uniknuných údajoch. Na ochranu svojho účtu používajte jedinečné heslo. Naozaj chcete používať odhalené heslo?" + "message": "Heslo bolo nájdené v uniknutých údajoch. Na ochranu svojho účtu používajte jedinečné heslo. Naozaj chcete používať uniknuté heslo?" }, "weakAndExposedMasterPassword": { - "message": "Slabé a odhalené hlavné heslo" + "message": "Slabé a uniknuté hlavné heslo" }, "weakAndBreachedMasterPasswordDesc": { - "message": "Nájdené slabé heslo v uniknuných údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" + "message": "Nájdené slabé heslo v uniknutých údajoch. Na ochranu svojho účtu používajte silné a jedinečné heslo. Naozaj chcete používať toto heslo?" }, "checkForBreaches": { "message": "Skontrolovať známe úniky údajov pre toto heslo" @@ -3222,7 +3219,7 @@ "message": "Vaše hlavné heslo sa nebude dať obnoviť, ak ho zabudnete!" }, "characterMinimum": { - "message": "Minimálny počet znakov $LENGTH$", + "message": "Minimálny počet znakov: $LENGTH$", "placeholders": { "length": { "content": "$1", @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgradovať na Prémium" + }, + "sessionTimeoutSettingsAction": { + "message": "Akcia pri vypršaní časového limitu" + }, + "sessionTimeoutHeader": { + "message": "Časový limit relácie" } } diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json index 329403e43fc..353c6858afa 100644 --- a/apps/desktop/src/locales/sl/messages.json +++ b/apps/desktop/src/locales/sl/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json index b7df1855552..1bc4a0ed016 100644 --- a/apps/desktop/src/locales/sr/messages.json +++ b/apps/desktop/src/locales/sr/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Немате дозволу да уређујете ову ставку" }, "welcomeBack": { "message": "Добродошли назад" @@ -775,7 +775,7 @@ "message": "Употребити једнократну пријаву" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша организација захтева јединствену пријаву." }, "submit": { "message": "Пошаљи" @@ -1039,7 +1039,7 @@ "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Везе морају да користе HTTPS." }, "customEnvironment": { "message": "Прилагођено окружење" @@ -1862,10 +1862,10 @@ "message": "Закључајте са главном лозинком при поновном покретању" }, "requireMasterPasswordOrPinOnAppRestart": { - "message": "Require master password or PIN on app restart" + "message": "Потражити главну лозинку или ПИН при поновном покретању" }, "requireMasterPasswordOnAppRestart": { - "message": "Require master password on app restart" + "message": "Потражити главну лозинку при поновном покретању апликације" }, "deleteAccount": { "message": "Брисање налога" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Дошло је до грешке при омогућавању интеграције прегледача." }, - "browserIntegrationMasOnlyDesc": { - "message": "Нажалост, интеграција прегледача за сада је подржана само у верзији Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Нажалост, интеграција прегледача није за сада подржана у Windows Store." }, @@ -4183,7 +4180,7 @@ "message": "Ставка је послата у архиву" }, "itemWasUnarchived": { - "message": "Item was unarchived" + "message": "Ставка враћена из архиве" }, "archiveItem": { "message": "Архивирај ставку" @@ -4192,33 +4189,39 @@ "message": "Архивиране ставке су искључене из општих резултата претраге и предлога за ауто попуњавање. Јесте ли сигурни да желите да архивирате ову ставку?" }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "ZIP/Поштански број" }, "cardNumberLabel": { - "message": "Card number" + "message": "Број картице" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Надогради сада" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Уграђени аутентификатор" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Сигурно складиштење датотека" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Хитан приступ" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Праћење повreda безбедности" }, "andMoreFeatures": { - "message": "And more!" + "message": "И још више!" }, "planDescPremium": { - "message": "Complete online security" + "message": "Потпуна онлајн безбедност" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Надоградите на Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json index e06f78efbf6..93d56419ae3 100644 --- a/apps/desktop/src/locales/sv/messages.json +++ b/apps/desktop/src/locales/sv/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Ett fel uppstod vid aktivering av webbläsarintegration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Tyvärr stöds webbläsarintegration för tillfället endast i versionen från Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Tyvärr stöds webbläsarintegration för tillfället inte i versionen från Windows Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Uppgradera till Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Tidsgränsåtgärd" + }, + "sessionTimeoutHeader": { + "message": "Sessionstidsgräns" } } diff --git a/apps/desktop/src/locales/ta/messages.json b/apps/desktop/src/locales/ta/messages.json index de574204738..2f9d12917d6 100644 --- a/apps/desktop/src/locales/ta/messages.json +++ b/apps/desktop/src/locales/ta/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "உலாவி ஒருங்கிணைப்பை இயக்கும்போது ஒரு பிழை ஏற்பட்டது." }, - "browserIntegrationMasOnlyDesc": { - "message": "துரதிர்ஷ்டவசமாக உலாவி ஒருங்கிணைப்பு தற்போது Mac App Store பதிப்பில் மட்டுமே ஆதரிக்கப்படுகிறது." - }, "browserIntegrationWindowsStoreDesc": { "message": "துரதிர்ஷ்டவசமாக உலாவி ஒருங்கிணைப்பு தற்போது Microsoft Store பதிப்பில் ஆதரிக்கப்படவில்லை." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json index 70d4c7cb494..d607bb8d097 100644 --- a/apps/desktop/src/locales/te/messages.json +++ b/apps/desktop/src/locales/te/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json index a9b197f946c..d794ace629c 100644 --- a/apps/desktop/src/locales/th/messages.json +++ b/apps/desktop/src/locales/th/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "An error has occurred while enabling browser integration." }, - "browserIntegrationMasOnlyDesc": { - "message": "Unfortunately browser integration is only supported in the Mac App Store version for now." - }, "browserIntegrationWindowsStoreDesc": { "message": "Unfortunately browser integration is currently not supported in the Microsoft Store version." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Upgrade to Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json index 94f64b31811..ac67b177cbf 100644 --- a/apps/desktop/src/locales/tr/messages.json +++ b/apps/desktop/src/locales/tr/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Tarayıcı entegrasyonu etkinleştirilirken bir hata oluştu." }, - "browserIntegrationMasOnlyDesc": { - "message": "Ne yazık ki tarayıcı entegrasyonu şu anda sadece Mac App Store sürümünde destekleniyor." - }, "browserIntegrationWindowsStoreDesc": { "message": "Maalesef tarayıcı entegrasyonu şimdilik Windows Store sürümünde desteklenmiyor." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Premium'a yükselt" + }, + "sessionTimeoutSettingsAction": { + "message": "Zaman aşımı eylemi" + }, + "sessionTimeoutHeader": { + "message": "Oturum zaman aşımı" } } diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json index b6e0b18a981..7ed0710ca74 100644 --- a/apps/desktop/src/locales/uk/messages.json +++ b/apps/desktop/src/locales/uk/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Під час увімкнення інтеграції з браузером сталася помилка." }, - "browserIntegrationMasOnlyDesc": { - "message": "На жаль, зараз інтеграція з браузером підтримується лише у версії для Mac з App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "На жаль, зараз інтеграція з браузером не підтримується у версії з Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Покращити до Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json index db945022070..8bf88aba458 100644 --- a/apps/desktop/src/locales/vi/messages.json +++ b/apps/desktop/src/locales/vi/messages.json @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "Đã xảy ra lỗi khi bật tích hợp với trình duyệt." }, - "browserIntegrationMasOnlyDesc": { - "message": "Rất tiếc, tính năng tích hợp trình duyệt hiện chỉ được hỗ trợ trong phiên bản Mac App Store." - }, "browserIntegrationWindowsStoreDesc": { "message": "Rất tiếc, tính năng tích hợp trình duyệt hiện không được hỗ trợ trong phiên bản Microsoft Store." }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" } } diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 93c907743d9..353fc036f63 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1298,7 +1298,7 @@ "message": "系统空闲时" }, "onSleep": { - "message": "系统休眠时" + "message": "系统睡眠时" }, "onLocked": { "message": "系统锁定时" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "启用浏览器集成时出错。" }, - "browserIntegrationMasOnlyDesc": { - "message": "很遗憾,目前仅 Mac App Store 版本支持浏览器集成。" - }, "browserIntegrationWindowsStoreDesc": { "message": "很遗憾,Microsoft Store 版本目前不支持浏览器集成。" }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "升级为高级版" + }, + "sessionTimeoutSettingsAction": { + "message": "超时动作" + }, + "sessionTimeoutHeader": { + "message": "会话超时" } } diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json index 0dc4d0911fc..61fc00543ed 100644 --- a/apps/desktop/src/locales/zh_TW/messages.json +++ b/apps/desktop/src/locales/zh_TW/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" }, "welcomeBack": { "message": "歡迎回來" @@ -2150,9 +2150,6 @@ "browserIntegrationErrorDesc": { "message": "啟用瀏覽器整合時發生錯誤。" }, - "browserIntegrationMasOnlyDesc": { - "message": "很遺憾,目前僅 Mac App Store 版本支援瀏覽器整合功能。" - }, "browserIntegrationWindowsStoreDesc": { "message": "很遺憾,Microsoft Store 版本目前尚不支援瀏覽器整合功能。" }, @@ -4220,5 +4217,11 @@ }, "upgradeToPremium": { "message": "升級到 Premium" + }, + "sessionTimeoutSettingsAction": { + "message": "逾時後動作" + }, + "sessionTimeoutHeader": { + "message": "工作階段逾時" } } From 9733ef0a3ef2cfb8ffadab23b6a4b31692cf119c Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:33:26 +0000 Subject: [PATCH 134/249] Autosync the updated translations (#17378) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 108 ++++++++- apps/web/src/locales/ar/messages.json | 108 ++++++++- apps/web/src/locales/az/messages.json | 188 +++++++++++----- apps/web/src/locales/be/messages.json | 108 ++++++++- apps/web/src/locales/bg/messages.json | 112 +++++++++- apps/web/src/locales/bn/messages.json | 108 ++++++++- apps/web/src/locales/bs/messages.json | 108 ++++++++- apps/web/src/locales/ca/messages.json | 108 ++++++++- apps/web/src/locales/cs/messages.json | 110 +++++++++- apps/web/src/locales/cy/messages.json | 108 ++++++++- apps/web/src/locales/da/messages.json | 108 ++++++++- apps/web/src/locales/de/messages.json | 112 +++++++++- apps/web/src/locales/el/messages.json | 108 ++++++++- apps/web/src/locales/en_GB/messages.json | 108 ++++++++- apps/web/src/locales/en_IN/messages.json | 108 ++++++++- apps/web/src/locales/eo/messages.json | 108 ++++++++- apps/web/src/locales/es/messages.json | 108 ++++++++- apps/web/src/locales/et/messages.json | 108 ++++++++- apps/web/src/locales/eu/messages.json | 108 ++++++++- apps/web/src/locales/fa/messages.json | 108 ++++++++- apps/web/src/locales/fi/messages.json | 108 ++++++++- apps/web/src/locales/fil/messages.json | 108 ++++++++- apps/web/src/locales/fr/messages.json | 110 +++++++++- apps/web/src/locales/gl/messages.json | 108 ++++++++- apps/web/src/locales/he/messages.json | 110 +++++++++- apps/web/src/locales/hi/messages.json | 108 ++++++++- apps/web/src/locales/hr/messages.json | 114 ++++++++-- apps/web/src/locales/hu/messages.json | 110 +++++++++- apps/web/src/locales/id/messages.json | 110 +++++++++- apps/web/src/locales/it/messages.json | 108 ++++++++- apps/web/src/locales/ja/messages.json | 108 ++++++++- apps/web/src/locales/ka/messages.json | 108 ++++++++- apps/web/src/locales/km/messages.json | 108 ++++++++- apps/web/src/locales/kn/messages.json | 108 ++++++++- apps/web/src/locales/ko/messages.json | 108 ++++++++- apps/web/src/locales/lv/messages.json | 126 +++++++++-- apps/web/src/locales/ml/messages.json | 108 ++++++++- apps/web/src/locales/mr/messages.json | 108 ++++++++- apps/web/src/locales/my/messages.json | 108 ++++++++- apps/web/src/locales/nb/messages.json | 108 ++++++++- apps/web/src/locales/ne/messages.json | 108 ++++++++- apps/web/src/locales/nl/messages.json | 108 ++++++++- apps/web/src/locales/nn/messages.json | 108 ++++++++- apps/web/src/locales/or/messages.json | 108 ++++++++- apps/web/src/locales/pl/messages.json | 108 ++++++++- apps/web/src/locales/pt_BR/messages.json | 108 ++++++++- apps/web/src/locales/pt_PT/messages.json | 108 ++++++++- apps/web/src/locales/ro/messages.json | 108 ++++++++- apps/web/src/locales/ru/messages.json | 108 ++++++++- apps/web/src/locales/si/messages.json | 108 ++++++++- apps/web/src/locales/sk/messages.json | 142 +++++++++--- apps/web/src/locales/sl/messages.json | 108 ++++++++- apps/web/src/locales/sr_CS/messages.json | 108 ++++++++- apps/web/src/locales/sr_CY/messages.json | 266 +++++++++++++++-------- apps/web/src/locales/sv/messages.json | 108 ++++++++- apps/web/src/locales/ta/messages.json | 108 ++++++++- apps/web/src/locales/te/messages.json | 108 ++++++++- apps/web/src/locales/th/messages.json | 108 ++++++++- apps/web/src/locales/tr/messages.json | 110 +++++++++- apps/web/src/locales/uk/messages.json | 108 ++++++++- apps/web/src/locales/vi/messages.json | 110 +++++++++- apps/web/src/locales/zh_CN/messages.json | 120 ++++++++-- apps/web/src/locales/zh_TW/messages.json | 116 ++++++++-- 63 files changed, 6343 insertions(+), 799 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index f63a0878540..684e107cd57 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Laai lisensie af" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 30eddbb1702..fb42b2b5fa2 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "الدفعة القادمة" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "التفاصيل" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "تنزيل الرخصة" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index a09fa41ed90..8dcfa862f03 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -21,7 +21,7 @@ "message": "Parol riski" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Bu elementə düzəliş etmə icazəniz yoxdur" }, "reviewAtRiskPasswords": { "message": "Tətbiqlər arasında riskli (zəif, ifşa olunmuş və ya təkrar istifadə olunmuş) parolları incələyin. İstifadəçilərinizin riskli parollara yönəlmiş təhlükəsizlik tədbirlərinə əhəmiyyət vermələri üçün kritik tətbiqlərinizi seçin." @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "İncələmə gözləyən müraciətlər" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ yeni müraciət", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Hazırda incələnməli yeni tətbiq yoxdur" }, + "organizationHasItemsSavedForApplications": { + "message": "Təşkilatınızda $COUNT$ tətbiq üçün saxlanılmış elementlər var", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Təşkilatınzıın təhlükəsizliyi üçün ən kritik sayılan elementləri güvəndə saxlamaq üçün tətbiqləri incələyin" + }, + "reviewApplications": { + "message": "Tətbiqləri incələ" + }, "prioritizeCriticalApplications": { "message": "Kritik tətbiqləri prioritetləşdir" }, @@ -784,7 +802,7 @@ } }, "passwordSafe": { - "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Rahatlıqla istifadə edə bilərsiniz." + "message": "Bu parol, veri pozuntularında qeydə alınmayıb. Əmniyyətlə istifadə edə bilərsiniz." }, "save": { "message": "Saxla" @@ -2491,7 +2509,7 @@ "message": "SSO quraşdırması etmisinizsə və ya etmək planınız varsa, İki addımlı giriş, artıq Kimlik Provayderiniz vasitəsilə tətbiq edilmiş ola bilər." }, "twoStepLoginRecoveryWarning": { - "message": "İki addımlı girişi qurmaq, Bitwarden hesabınızı birdəfəlik kilidləyə bilər. Geri qaytarma kodu, normal iki addımlı giriş provayderinizi artıq istifadə edə bilmədiyiniz hallarda (məs. cihazınızı itirəndə) hesabınıza erişməyinizə imkan verir. Hesabınıza erişimi itirsəniz, Bitwarden dəstəyi sizə kömək edə bilməyəcək. Geri qaytarma kodunuzu bir yerə yazmağınızı və ya çap etməyinizi və onu etibarlı bir yerdə saxlamağınızı məsləhət görürük." + "message": "İki addımlı girişi qurmaq, Bitwarden hesabınızı birdəfəlik kilidləyə bilər. Geri qaytarma kodu, normal iki addımlı giriş provayderinizi artıq istifadə edə bilmədiyiniz hallarda (məs. cihazınızı itirəndə) hesabınıza erişməyinizə imkan verir. Hesabınıza erişimi itirsəniz, Bitwarden dəstəyi sizə kömək edə bilməyəcək. Geri qaytarma kodunuzu bir yerə yazmağınızı və ya çap etməyinizi və onu əmniyyətli bir yerdə saxlamağınızı məsləhət görürük." }, "restrictedItemTypePolicy": { "message": "Kart element növünü sil" @@ -2506,7 +2524,7 @@ "message": "1 və ya daha çox təşkilat tərəfindən təyin edilən bir siyasət, kartların seyfinizə köçürülməsini əngəlləyir." }, "yourSingleUseRecoveryCode": { - "message": "İki addımlı giriş provayderinizə erişə bilmədiyiniz halda, iki addımlı girişi söndürmək üçün təkistifadəlik geri qaytarma kodunu istifadə edə bilərsiniz. Bitwarden tövsiyə edir ki, geri qaytarma kodunuzu bir yerə yazıb güvənli bir yerdə saxlayın." + "message": "İki addımlı giriş provayderinizə erişə bilmədiyiniz halda, iki addımlı girişi söndürmək üçün təkistifadəlik geri qaytarma kodunu istifadə edə bilərsiniz. Bitwarden tövsiyə edir ki, geri qaytarma kodunuzu bir yerə yazıb əmniyyətli bir yerdə saxlayın." }, "viewRecoveryCode": { "message": "Geri qaytarma koduna bax" @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Növbəti ödəniş" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Təfsilatlar" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Lisenziyanı endir" }, @@ -3257,7 +3284,7 @@ "message": "Saxlama sahəsi əlavə et" }, "removeStorage": { - "message": "Saxlama sahəsini çıxart" + "message": "Saxlama sahəsini xaric et" }, "subscriptionStorage": { "message": "Abunəliyinizdə cəmi $MAX_STORAGE$ GB şifrələnmiş fayl saxlama sahəsi var. Hazırda $USED_STORAGE$ istifadə edirsiniz.", @@ -3329,7 +3356,7 @@ "message": "GB saxlamaya əlavə et" }, "gbStorageRemove": { - "message": "GB saxlamadan çıxart" + "message": "Anbardan xaric ediləcək GB" }, "storageAddNote": { "message": "Saxlama sahəsi əlavə etmək, fakturanın cəmində dəyişikliklərə səbəb olacaq və dərhal hesab ödəniş metodunuzdan çıxılacaq. İlk çıxılacaq hesab, hazırkı faktura dövrünün qalanı üçün etibarlı olacaq." @@ -3670,10 +3697,10 @@ } }, "removeUserConfirmation": { - "message": "Bu istifadəçini çıxartmaq istədiyinizə əminsiniz?" + "message": "Bu istifadəçini xaric etmək istədiyinizə əminsiniz?" }, "removeOrgUserConfirmation": { - "message": "Bir üzv silindikdə, artıq həmin üzv təşkilat verilərinə erişə bilmir və bu əməliyyatın geri dönüşü yoxdur. Həmin üzvü təşkilata yenidən əlavə etmək üçün, onu dəvət edib üzv olmasını təmin etmək lazımdır." + "message": "Bir üzv xaric edildiyi zaman, artıq həmin üzv təşkilat verilərinə erişə bilmir və bu əməliyyatın geri dönüşü yoxdur. Həmin üzvü təşkilata yenidən əlavə etmək üçün, onu dəvət edib üzv olmasını təmin etmək lazımdır." }, "revokeUserConfirmation": { "message": "Bir üzv ləğv edildikdə, artıq həmin üzv təşkilat verilərinə erişə bilmir. Üzv erişimini daha tez bərpa etmək üçün, Ləğv edilənlər vərəqinə gedin." @@ -4094,7 +4121,7 @@ } }, "removedUserId": { - "message": "$ID$ istifadəçisi çıxarıldı.", + "message": "$ID$ istifadəçisi xaric edildi.", "placeholders": { "id": { "content": "$1", @@ -4103,7 +4130,7 @@ } }, "removeUserIdAccess": { - "message": "$ID$ erişimini sil", + "message": "$ID$ erişimini xaric et", "placeholders": { "id": { "content": "$1", @@ -4232,7 +4259,7 @@ } }, "removedOrganizationId": { - "message": "$ID$ təşkilatı silindi.", + "message": "$ID$ təşkilatı xaric edildi.", "placeholders": { "id": { "content": "$1", @@ -4440,7 +4467,31 @@ "message": "Brauzeri güncəllə" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Access Intelligence-niz yaradılır..." + }, + "fetchingMemberData": { + "message": "Üzv veriləri alınır..." + }, + "analyzingPasswordHealth": { + "message": "Parol sağlamlığı analiz edirilir..." + }, + "calculatingRiskScores": { + "message": "Risk xalı hesablanır..." + }, + "generatingReportData": { + "message": "Hesabat veriləri yaradılır..." + }, + "savingReport": { + "message": "Hesabat saxlanılır..." + }, + "compilingInsights": { + "message": "Təhlillər şərh edilir..." + }, + "loadingProgress": { + "message": "İrəliləyiş yüklənir" + }, + "thisMightTakeFewMinutes": { + "message": "Bu, bir neçə dəqiqə çəkə bilər." }, "riskInsightsRunReport": { "message": "Hesabatı işə sal" @@ -4716,7 +4767,7 @@ "description": "Seat = User Seat" }, "removeSeats": { - "message": "Yeri götür", + "message": "Yeri xaric et", "description": "Seat = User Seat" }, "subscriptionDesc": { @@ -4825,7 +4876,7 @@ "message": "Əlavə ediləcək yerlər" }, "seatsToRemove": { - "message": "Götürüləcək yerlər" + "message": "Xaric ediləcək yerlər" }, "seatsAddNote": { "message": "İstifadəçi yerləri əlavə etmək, fakturanın cəmində dəyişikliklərə səbəb olacaq və dərhal hesab ödəniş metodunuzdan çıxılacaq. İlk çıxılacaq hesab, hazırkı faktura dövrünün qalanı üçün etibarlı olacaq." @@ -5178,10 +5229,10 @@ "message": "İstifadəçilərin \"iki addımlı giriş\"i qurmasını tələb et." }, "twoStepLoginPolicyWarning": { - "message": "Sahib və ya Administrator olmayan və fərdi hesablarında \"iki addımlı giriş\"i fəallaşdırmayan təşkilat üzvləri təşkilatdan çıxarılacaq və dəyişiklik haqqında onları məlumatlandıran e-poçt göndəriləcək." + "message": "Sahib və ya Administrator olmayan və fərdi hesablarında \"iki addımlı giriş\"i fəallaşdırmayan təşkilat üzvləri təşkilatdan xaric ediləcək və dəyişiklik haqqında onları məlumatlandıran e-poçt göndəriləcək." }, "twoStepLoginPolicyUserWarning": { - "message": "İstifadəçi hesabında \"iki addımlı giriş\"in qurulmasını tələb edən təşkilatın üzvüsünüz. İki addımlı giriş provayderlərinin hamısını söndürsəniz, bu təşkilatdan avtomatik olaraq çıxarılacaqsınız." + "message": "İstifadəçi hesabında \"iki addımlı giriş\"in qurulmasını tələb edən təşkilatın üzvüsünüz. İki addımlı giriş provayderlərinin hamısını söndürsəniz, bu təşkilatdan avtomatik olaraq xaric ediləcəksiniz." }, "passwordGeneratorPolicyDesc": { "message": "Parol yaradıcı üçün tələbləri ayarla." @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Avtomatik istifadəçi təsdiqi necə işə salınır" }, - "autoConfirmStep1": { - "message": "Bitwarden uzantınızı açın." + "autoConfirmExtension1": { + "message": "Bitwarden uzantınızı açın" }, - "autoConfirmStep2a": { - "message": "Seçin", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Seç:", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " İşə sal.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " İşə sal", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Bitwarden brauzer uzantısı uğurla açıldı. Artıq avtomatik istifadəçi təsdiqi ayarını aktivləşdirə bilərsiniz." @@ -5851,7 +5902,7 @@ "message": "Bu riskləri və siyasət güncəlləmələrini qəbul edirəm" }, "personalOwnership": { - "message": "Fərdi sahiblik" + "message": "Fərdi seyfi xaric et" }, "personalOwnershipPolicyDesc": { "message": "Fərdi seyf seçimini silərək istifadəçilərin elementləri bir təşkilatda saxlamasını məcburi edin." @@ -5870,7 +5921,7 @@ "description": "This policy will enable Desktop Autotype by default for members on Unlock." }, "disableSend": { - "message": "\"Send\"i sıradan çıxart" + "message": "\"Send\"i sil" }, "disableSendPolicyDesc": { "message": "İstifadəçilərin Bitwarden Send yaratmasına və ya ona düzəliş etməsinə icazə vermə. Mövcud \"Send\"in silinməsinə hələ də icazə verilir.", @@ -5880,7 +5931,7 @@ "message": "Təşkilatın siyasətlərini idarə edə bilən təşkilat istifadəçiləri bu siyasətin tətbiqindən azaddırlar." }, "sendDisabled": { - "message": "Send sıradan çıxarıldı", + "message": "Send silindi", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendDisabledWarning": { @@ -6012,7 +6063,7 @@ "message": "Bir təşkilat siyasəti, elementlərin fərdi seyfinizə köçürülməsini əngəllədi." }, "personalOwnershipCheckboxDesc": { - "message": "Təşkilat istifadəçiləri üçün fərdi sahibliyi sıradan çıxart" + "message": "Təşkilat istifadəçiləri üçün fərdi sahibliyi sil" }, "send": { "message": "Send", @@ -6337,10 +6388,10 @@ "message": "Bu əməliyyat, seçilən istifadəçilərin heç biri üçün etibarlı deyil." }, "removeUsersWarning": { - "message": "Aşağıdakı istifadəçiləri çıxartmaq istədiyinizə əminsiniz? Bu prosesin tamamlanması bir neçə saniyə çəkir, ləğv edilə və ya dayandırıla bilməz." + "message": "Aşağıdakı istifadəçiləri xaric etmək istədiyinizə əminsiniz? Bu prosesin tamamlanması bir neçə saniyə çəkir, ləğv edilə və ya dayandırıla bilməz." }, "removeOrgUsersConfirmation": { - "message": "Üzv(lər) silindikdə, artıq həmin üzv(lər) təşkilat verilərinə erişə bilmir və bu əməliyyatın geri dönüşü yoxdur. Üzvü təşkilata yenidən əlavə etmək üçün, onu dəvət edib üzv olmasını təmin etmək lazımdır. Prosesin tamamlanması bir neçə saniyə çəkə bilər, bu proses dayandırıla və ya ləğv edilə bilməz." + "message": "Üzv(lər) xaric edildiyi zaman, artıq həmin üzv(lər) təşkilat verilərinə erişə bilmir və bu əməliyyatın geri dönüşü yoxdur. Üzvü təşkilata yenidən əlavə etmək üçün, onu dəvət edib üzv olmasını təmin etmək lazımdır. Prosesin tamamlanması bir neçə saniyə çəkə bilər, bu proses dayandırıla və ya ləğv edilə bilməz." }, "revokeUsersWarning": { "message": "Üzv(lər) ləğv edildikə, artıq həmin üzv(lər) təşkilat verilərinə erişə bilmir. Üzv erişimini daha tez bərpa etmək üçün, Ləğv edilənlər vərəqinə gedin. Prosesin tamamlanması bir neçə saniyə çəkə bilər, bu proses dayandırıla və ya ləğv edilə bilməz." @@ -6584,31 +6635,31 @@ "message": "Təşkilatınız, şifrə açma seçimlərinizi güncəllədi. Seyfinizə erişmək üçün lütfən bir ana parol təyin edin." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Seans vaxt bitməsi" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Sahiblər istisna olmaqla bütün üzvlər üçün maksimum seans bitmə vaxtını təyin edin." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Maksimum icazə verilən bitmə vaxtı" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "İcazə verilən maksimum bitmə vaxtı tələb olunur." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Vaxt etibarsızdır. Ən azı bir dəyəri dəyişdirin." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Seans vaxt bitmə əməliyyatı" }, "immediately": { - "message": "Immediately" + "message": "Dərhal" }, "onSystemLock": { - "message": "On system lock" + "message": "Sistem kilidlənəndə" }, "onAppRestart": { - "message": "On app restart" + "message": "Tətbiq yenidən başladılanda" }, "hours": { "message": "Saat" @@ -6617,7 +6668,7 @@ "message": "Dəqiqə" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Bütün üzvlər üçün maksimum bitmə vaxtını \"Heç vaxt\" olaraq icazə vermək istədiyinizə əminsiniz?" }, "sessionTimeoutConfirmationNeverDescription": { "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." @@ -6909,7 +6960,7 @@ "message": "\"Bitwarden Ailələri\"ni istifadə etmək üçün fərdi e-poçtunuzu daxil edin" }, "sponsoredFamiliesLeaveCopy": { - "message": "Bu təşkilatı tərk etsəniz və ya bu təşkilatdan çıxarılsanız, Ailələr planınızın istifadə müddəti faktura dövrünün sonunda başa çatacaq." + "message": "Bu təşkilatı tərk etsəniz və ya bu təşkilatdan xaric edilsəniz, Ailələr planınızın istifadə müddəti faktura dövrünün sonunda başa çatacaq." }, "acceptBitwardenFamiliesHelp": { "message": "Mövcud bir təşkilat üçün bir təklifi qəbul edin və ya yeni bir Ailələr təşkilatını yaradın." @@ -8407,7 +8458,7 @@ "message": "Rol" }, "removeMember": { - "message": "Üzv sil" + "message": "Üzv xaric et" }, "collection": { "message": "Kolleksiya" @@ -8913,7 +8964,7 @@ } }, "removedUserToServiceAccountWithId": { - "message": "$USER_ID$ istifadəçisi, $SERVICE_ACCOUNT_ID$ ID-sinə sahib maşın hesabından çıxarıldı", + "message": "$USER_ID$ istifadəçisi, $SERVICE_ACCOUNT_ID$ ID-sinə sahib maşın hesabından xaric edildi", "placeholders": { "user_id": { "content": "$1", @@ -8926,7 +8977,7 @@ } }, "removedGroupFromServiceAccountWithId": { - "message": "$GROUP_ID$ qrupu, $SERVICE_ACCOUNT_ID$ ID-sinə sahib maşın hesabından çıxarıldı", + "message": "$GROUP_ID$ qrupu, $SERVICE_ACCOUNT_ID$ ID-sinə sahib maşın hesabından xaric edildi", "placeholders": { "group_id": { "content": "$1", @@ -9054,7 +9105,7 @@ "message": "Erişim tokenləri hələ də mövcuddur" }, "saPeopleWarningMessage": { - "message": "İnsanları xidmət hesabından silsəniz belə, yaratdıqları erişim tokenləri silinmir. Ən yaxşı təhlükəsizlik təcrübəsi üçün, xidmət hesabından silinmiş insanlar tərəfindən yaradılan erişim tokenlərinin ləğv edilməsi tövsiyə olunur." + "message": "İnsanları xidmət hesabından xaric etsəniz belə, yaratdıqları erişim tokenləri silinmir. Ən yaxşı təhlükəsizlik təcrübəsi üçün, xidmət hesabından xaric edilmiş insanlar tərəfindən yaradılan erişim tokenlərinin ləğv edilməsi tövsiyə olunur." }, "smAccessRemovalWarningProjectTitle": { "message": "Bu layihəyə erişimi sil" @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Giriş edildi!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Kolleksiya erişimini təyin et" }, @@ -11528,7 +11576,7 @@ "message": "Yeni biznes vahidi" }, "sendsTitleNoItems": { - "message": "Send, həssas məlumatlar təhlükəsizdir", + "message": "Send ilə həssas məlumatlar əmniyyətdədir", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "sendsBodyNoItems": { @@ -11583,7 +11631,7 @@ "message": "Kimliklərinizlə, uzun qeydiyyat və ya əlaqə xanalarını daha tez avtomatik doldurun." }, "newNoteNudgeTitle": { - "message": "Həssas verilərinizi güvənli şəkildə saxlayın" + "message": "Həssas verilərinizi əmniyyətdə saxlayın" }, "newNoteNudgeBody": { "message": "Notlarla, bankçılıq və ya sığorta təfsilatları kimi həssas veriləri təhlükəsiz saxlayın." @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Ödənişsiz Ailələr sınağını başlat" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Seyf vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Müəssisə siyasət tələbləri, vaxt bitmə seçimlərinizə tətbiq edildi" + }, + "vaultTimeoutTooLarge": { + "message": "Seyfin bitmə vaxtı, təşkilatınız tərəfindən ayarlanan məhdudiyyətləri aşır." + }, + "neverLockWarning": { + "message": "\"Heç vaxt\"i seçmək istədiyinizə əminsiniz? Kilid seçimini \"Heç vaxt\" olaraq ayarlasanız, seyfinizin şifrələmə açarı cihazınızda saxlanılacaq. Bu seçimi istifadə etsəniz, cihazınızı daha yaxşı mühafizə etdiyinizə əmin olmalısınız." + }, + "sessionTimeoutSettingsAction": { + "message": "Vaxt bitmə əməliyyatı" + }, + "sessionTimeoutHeader": { + "message": "Sessiya vaxt bitməsi" + }, + "appearance": { + "message": "Görünüş" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Vaxt bitməsinə təyin olunan vaxt, təşkilatınız tərəfindən ayarlanan məhdudiyyəti aşır: Maksimum $HOURS$ saat və $MINUTES$ dəqiqə", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 21639cddbcc..9fd22d0ccd0 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Наступнае спагнанне" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Падрабязнасці" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Спампаваць ліцэнзію" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Вы ўвайшлі!" }, - "beta": { - "message": "Бэта" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index cc6e298c26e..6f17d4b264b 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -15,7 +15,7 @@ "message": "Няма важни приложения в риск" }, "accessIntelligence": { - "message": "Access Intelligence" + "message": "Анализ на достъпа" }, "passwordRisk": { "message": "Рискова парола" @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Приложения, които имат нужда от преглед" }, + "newApplicationsCardTitle": { + "message": "Преглед на новите приложения" + }, "newApplicationsWithCount": { "message": "$COUNT$ нови приложения", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "В момента няма нови приложения за преглед" }, + "organizationHasItemsSavedForApplications": { + "message": "Вашата организация има запазени елементи за $COUNT$ приложения", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Прегледайте приложенията, за да защитите най-важните за организацията си елементи" + }, + "reviewApplications": { + "message": "Преглед на приложенията" + }, "prioritizeCriticalApplications": { "message": "Даване на приоритет на важните приложения" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Следваща промяна" }, + "nextChargeHeader": { + "message": "Следващо плащане" + }, + "plan": { + "message": "План" + }, "details": { "message": "Детайли" }, + "discount": { + "message": "отстъпка" + }, "downloadLicense": { "message": "Изтегляне на лиценз" }, @@ -4440,7 +4467,31 @@ "message": "Обновяване на браузъра" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Създаване на Вашия анализ на достъпа…" + }, + "fetchingMemberData": { + "message": "Извличане на данните за членовете…" + }, + "analyzingPasswordHealth": { + "message": "Анализиране на състоянието на паролите…" + }, + "calculatingRiskScores": { + "message": "Изчисляване на оценките на риска…" + }, + "generatingReportData": { + "message": "Създаване на данните за доклада…" + }, + "savingReport": { + "message": "Запазване на доклада…" + }, + "compilingInsights": { + "message": "Събиране на подробности…" + }, + "loadingProgress": { + "message": "Зареждане на напредъка" + }, + "thisMightTakeFewMinutes": { + "message": "Това може да отнеме няколко минути." }, "riskInsightsRunReport": { "message": "Изпълнение на доклада" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Как се включва автоматичното потвърждаване на потребителите" }, - "autoConfirmStep1": { - "message": "Отворете добавката на Битуорден." + "autoConfirmExtension1": { + "message": "Отворете добавката на Битуорден" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Изберете", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Включване.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Включване", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Добавката за браузър на Битуорден е отворена. Сега можете да включите настройката за автоматично потвърждаване на потребителите." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Вписахте се!" }, - "beta": { - "message": "Бета" - }, "assignCollectionAccess": { "message": "Задаване на достъп за колекциите" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Започнете безплатния пробен период на Семейния план" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп до трезора." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Изискванията на политиката за големи компании бяха приложени към настройките на времето за достъп" + }, + "vaultTimeoutTooLarge": { + "message": "Времето за достъп до трезора Ви превишава ограничението, определено от организацията Ви." + }, + "neverLockWarning": { + "message": "Уверени ли сте, че искате да зададете стойност „Никога“? Това води до съхранение на шифриращия ключ за трезора във устройството ви. Ако използвате тази възможност, е много важно да имате надлежна защита на устройството си." + }, + "sessionTimeoutSettingsAction": { + "message": "Действие при изтичането на времето за достъп" + }, + "sessionTimeoutHeader": { + "message": "Изтичане на времето за сесията" + }, + "appearance": { + "message": "Външен вид" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Времето за достъп превишава ограничението, зададено от Вашата организация: максимум $HOURS$ час(а) и $MINUTES$ минута/и", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index ec567fd43e3..4baf480b517 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index c2150c22154..f9b5481cd23 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 7b0094fdecd..2994137855e 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Càrrec següent" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detall" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Baixa la llicència" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Connectat!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assigna accés a la col·lecció" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index bb5958b4e59..ed82a38c390 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplikace, které vyžadují kontrolu" }, + "newApplicationsCardTitle": { + "message": "Zkontrolovat nové aplikace" + }, "newApplicationsWithCount": { "message": "$COUNT$ nových aplikací", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Nyní nejsou k dispozici žádné nové kontroly" }, + "organizationHasItemsSavedForApplications": { + "message": "Vaše organizace má položky uložené pro $COUNT$ aplikací", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Zkontrolujte aplikace pro zabezpečení položek nejkritičtějších pro bezpečnost Vaší organizace" + }, + "reviewApplications": { + "message": "Zkontrolovat aplikace" + }, "prioritizeCriticalApplications": { "message": "Upřednostnit kritické aplikace" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Další platba" }, + "nextChargeHeader": { + "message": "Další platba" + }, + "plan": { + "message": "Plán" + }, "details": { "message": "Podrobnosti" }, + "discount": { + "message": "sleva" + }, "downloadLicense": { "message": "Stáhnout licenci" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generování Vaší přístupové inteligence..." }, + "fetchingMemberData": { + "message": "Načítání dat člena..." + }, + "analyzingPasswordHealth": { + "message": "Analyzování zdraví hesla..." + }, + "calculatingRiskScores": { + "message": "Výpočet skóre rizik..." + }, + "generatingReportData": { + "message": "Generování dat hlášení..." + }, + "savingReport": { + "message": "Ukládání hlášení..." + }, + "compilingInsights": { + "message": "Sestavování přehledů..." + }, + "loadingProgress": { + "message": "Průběh načítání" + }, + "thisMightTakeFewMinutes": { + "message": "Může to trvat několik minut." + }, "riskInsightsRunReport": { "message": "Spustit hlášení" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Jak zapnout automatické potvrzení uživatele" }, - "autoConfirmStep1": { - "message": "Otevřete rozšíření Bitwarden." + "autoConfirmExtension1": { + "message": "Otevřít rozšíření Bitwarden" }, - "autoConfirmStep2a": { - "message": "Vyberte", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Vybrat", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Zapnout.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Zapnout", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Rozšíření prohlížeče Bitwarden bylo úspěšně otevřeno. Nyní můžete aktivovat automatické potvrzení uživatele." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Přihlášeno!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Přiřadit přístup ke sbírce" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Zahájit bezplatnou zkušební verzi pro rodiny" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Nastavte metodu odemknutí, abyste změnili časový limit Vašeho trezoru." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Na volby časového limitu byly uplatněny požadavky podnikových zásad" + }, + "vaultTimeoutTooLarge": { + "message": "Časový limit Vašeho trezoru překračuje omezení stanovená Vaší organizací." + }, + "neverLockWarning": { + "message": "Opravdu chcete použít volbu \"Nikdy\"? Nastavením volby uzamčení na \"Nikdy\" bude šifrovací klíč k trezoru uložen přímo ve Vašem zařízení. Pokud tuto možnost použijete, měli byste Vaše zařízení řádně zabezpečit a chránit." + }, + "sessionTimeoutSettingsAction": { + "message": "Akce vypršení časového limitu" + }, + "sessionTimeoutHeader": { + "message": "Časový limit relace" + }, + "appearance": { + "message": "Vzhled" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Časový limit překračuje omezení stanovené Vaší organizací: maximálně $HOURS$ hodin a $MINUTES$ minut", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Nejsou vybrány žádné kritické aplikace" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Opravdu chcete pokračovat?" } } diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index cf5270e3d74..8c62a789fbd 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 543f846b078..7ddd60a2b75 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Næste betaling" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detaljer" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download licens" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Indlogget!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Tildel samlingsadgang" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 57f2a8699f5..162eefe9923 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Anwendungen, die geprüft werden müssen" }, + "newApplicationsCardTitle": { + "message": "Neue Anwendungen überprüfen" + }, "newApplicationsWithCount": { "message": "$COUNT$ neue Anwendungen", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Derzeit gibt es keine neuen Anwendungen zum Überprüfen" }, + "organizationHasItemsSavedForApplications": { + "message": "Deine Organisation hat Einträge für $COUNT$ Anwendungen gespeichert", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Überprüfe Anwendungen, um die für die Sicherheit deiner Organisation wichtigsten Einträge zu sichern" + }, + "reviewApplications": { + "message": "Anwendungen überprüfen" + }, "prioritizeCriticalApplications": { "message": "Kritische Anwendungen priorisieren" }, @@ -1387,7 +1405,7 @@ "message": "Mit Passkey anmelden" }, "useSingleSignOn": { - "message": "Single Sign-on verwenden" + "message": "Single Sign-On verwenden" }, "yourOrganizationRequiresSingleSignOn": { "message": "Deine Organisation erfordert Single Sign-On." @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Nächste Abbuchung" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Lizenz herunterladen" }, @@ -3640,7 +3667,7 @@ "message": "Richtlinien" }, "singleSignOn": { - "message": "Single Sign-on" + "message": "Single Sign-On" }, "editPolicy": { "message": "Richtlinie bearbeiten" @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Deine Zugangsintelligenz wird generiert..." }, + "fetchingMemberData": { + "message": "Mitgliedsdaten werden abgerufen..." + }, + "analyzingPasswordHealth": { + "message": "Passwortsicherheit wird analysiert..." + }, + "calculatingRiskScores": { + "message": "Risikobewertung wird berechnet..." + }, + "generatingReportData": { + "message": "Berichtsdaten werden generiert..." + }, + "savingReport": { + "message": "Bericht wird gespeichert..." + }, + "compilingInsights": { + "message": "Analyse wird zusammengestellt..." + }, + "loadingProgress": { + "message": "Ladefortschritt" + }, + "thisMightTakeFewMinutes": { + "message": "Dies kann einige Minuten dauern." + }, "riskInsightsRunReport": { "message": "Bericht ausführen" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "So aktivierst du die automatische Benutzerbestätigung" }, - "autoConfirmStep1": { - "message": "Öffne deine Bitwarden-Erweiterung." + "autoConfirmExtension1": { + "message": "Öffne deine Bitwarden-Erweiterung" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Wähle", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Aktivieren.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Einschalten", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Die Bitwarden Browser-Erweiterung wurde erfolgreich geöffnet. Du kannst nun die automatische Benutzerbestätigung aktivieren." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Angemeldet!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Sammlungszugriff zuweisen" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Kostenlose Families-Testversion starten" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Richte eine Entsperrmethode ein, um deine Aktion bei Tresor-Timeout zu ändern." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Die Unternehmens-Richtlinienanforderungen wurden auf deine Timeout-Optionen angewendet" + }, + "vaultTimeoutTooLarge": { + "message": "Dein Tresor-Timeout überschreitet die von deinem Unternehmen festgelegten Beschränkungen." + }, + "neverLockWarning": { + "message": "Bist du sicher, dass du die Option \"Nie\" verwenden möchtest? Durch das Setzen der Sperroptionen zu \"Nie\" wird der Verschlüsselungsschlüssel deines Tresors auf deinem Gerät gespeichert. Wenn du diese Option verwendest, solltest du sicherstellen, dass dein Gerät ausreichend geschützt ist." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout-Aktion" + }, + "sessionTimeoutHeader": { + "message": "Sitzungs-Timeout" + }, + "appearance": { + "message": "Aussehen" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Das Timeout überschreitet die von deiner Organisation festgelegte Beschränkung: Maximal $HOURS$ Stunde(n) und $MINUTES$ Minute(n)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 641c789f363..fb066120434 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Επόμενη Χρέωση" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Λεπτομέρειες" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Λήψη Άδειας" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Έχετε συνδεθεί!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Ανάθεση πρόσβασης συλλογής" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 01b96d1d207..2270d95056c 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organisation has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organisation's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download licence" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analysing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organisation." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organisation: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index c1da6986bba..99ebcf22413 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organisation has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organisation's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download licence" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analysing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organisation." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organisation: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 909ac5df6f6..b1acc78487d 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Sekva Akuzo" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detaloj" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Elŝuti Permesilon" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Jam salutis!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index adb547b4982..80767d520e8 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Siguiente cobro" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detalles" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Descargar licencia" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "¡Ha iniciado sesión!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Asignar acceso a colecciones" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index c26994bcf21..0e47bce83ff 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Järgmine makse" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Andmed" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Laadi litsents alla" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 58282adf95f..95281e381ca 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Hurrengo kobrantza" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Xehetasunak" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Lizentzia deskargatu" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index cf497f9ac86..aec55daad51 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "شارژ بعدی" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "جزئیات" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "دانلود مجوز" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "وارد شده!" }, - "beta": { - "message": "آزمایشی" - }, "assignCollectionAccess": { "message": "اختصاص دسترسی به مجموعه" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 383572bb5d7..8a1c7550d42 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Seuraava veloitus" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Tiedot" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Lataa lisenssi" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Kirjautuminen onnistui" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Määritä kokoelmien käyttöoikeudet" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index e912c206e60..6b0d5e3e8a5 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Susunod na singil" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Mga Detalye" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Mag-download ng lisensya" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 14c04bde667..8d51d685d79 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications nécessitant un examen" }, + "newApplicationsCardTitle": { + "message": "Examiner les nouvelles applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ nouvelles applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Aucune nouvelle application à examiner pour le moment" }, + "organizationHasItemsSavedForApplications": { + "message": "Votre organisation a des éléments enregistrés pour $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Examiner les applications pour sécuriser les éléments les plus critiques pour la sécurité de votre organisation" + }, + "reviewApplications": { + "message": "Examiner les applications" + }, "prioritizeCriticalApplications": { "message": "Prioriser les applications critiques" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Prochain paiement" }, + "nextChargeHeader": { + "message": "Prochain paiement" + }, + "plan": { + "message": "Forfait" + }, "details": { "message": "Détails" }, + "discount": { + "message": "réduction" + }, "downloadLicense": { "message": "Télécharger la licence" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Génération de votre Intelligence d'Accès..." }, + "fetchingMemberData": { + "message": "Récupération des données des membres..." + }, + "analyzingPasswordHealth": { + "message": "Analyse de la santé du mot de passe..." + }, + "calculatingRiskScores": { + "message": "Calcul des niveaux de risque..." + }, + "generatingReportData": { + "message": "Génération des données du rapport..." + }, + "savingReport": { + "message": "Enregistrement du rapport..." + }, + "compilingInsights": { + "message": "Compilation des aperçus..." + }, + "loadingProgress": { + "message": "Chargement de la progression" + }, + "thisMightTakeFewMinutes": { + "message": "Cela peut prendre quelques minutes." + }, "riskInsightsRunReport": { "message": "Exécuter le rapport" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Comment activer la confirmation automatique de l'utilisateur" }, - "autoConfirmStep1": { - "message": "Ouvrez votre extension Bitwarden." + "autoConfirmExtension1": { + "message": "Ouvrez votre extension Bitwarden" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Sélectionner", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Activer.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Activer", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Ouverture réussie de l'extension du navigateur Bitwarden. Vous pouvez maintenant activer le paramètre de confirmation automatique de l'utilisateur." @@ -6406,7 +6457,7 @@ "message": "Bitwarden n'a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." }, "contactCSToAvoidDataLossPart1": { - "message": "Contacter Customer Success", + "message": "Contacter Succès Client", "description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'" }, "contactCSToAvoidDataLossPart2": { @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Connecté !" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assigner l'accès à la collection" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Commencez l'essai gratuit au forfait Familles" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Les exigences de la politique de sécurité d'Entreprise ont été appliquées à vos options de délai d'expiration" + }, + "vaultTimeoutTooLarge": { + "message": "Le délai d'expiration de votre coffre dépasse les restrictions définies par votre organisation." + }, + "neverLockWarning": { + "message": "Êtes-vous sûr de vouloir utiliser l'option \"Jamais\" ? Définir le verrouillage sur \"Jamais\" stocke la clé de chiffrement de votre coffre sur votre appareil. Si vous utilisez cette option, vous devez vous assurer de correctement protéger votre appareil." + }, + "sessionTimeoutSettingsAction": { + "message": "Action à l’expiration" + }, + "sessionTimeoutHeader": { + "message": "Expiration de la session" + }, + "appearance": { + "message": "Apparence" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Le délai d'expiration dépasse la restriction définie par votre organisation : $HOURS$ heure(s) et $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index 0a9c69acd9b..e5c8ce9b49f 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 473380c7e56..b60e76f6a75 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "יישומים צריכים סקירה" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ יישומים חדשים", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "תעדוף יישומים קריטיים" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "החיוב הבא" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "פרטים" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "הורד רישיון" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "הרץ דוח" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "איך להפעיל אישור משתמש אוטומטי" }, - "autoConfirmStep1": { - "message": "פתח את הרחבת Bitwarden שלך." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { - "message": "בחר", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Select", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " הפעל.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "הרחבת Bitwarden לדפדפן נפתחה בהצלחה. כעת ביכולתך להפעיל את הגדרת אישור אוטומטי של משתמשים." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "נכנסת!" }, - "beta": { - "message": "בטא" - }, "assignCollectionAccess": { "message": "הקצה גישה לאוסף" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "התחל ניסיון משפחות בחינם" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 98d9896dd00..01cf40c3c0d 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index e97c0bdb040..e0f0ce0b28b 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplikacije koje je potrebno pregledati" }, + "newApplicationsCardTitle": { + "message": "Pregledaj nove prijave" + }, "newApplicationsWithCount": { "message": "Novih aplikacija: $COUNT$", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Trenutno nema novih aplikacija za pregled" }, + "organizationHasItemsSavedForApplications": { + "message": "Tvoja organizacija ima spremljene stavke za ovoliko aplikacija: $COUNT$", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Pregledaj aplikacije za zaštitu stavki najvažnijih za sigurnost tvoje organizacije" + }, + "reviewApplications": { + "message": "Pregledaj aplikacije" + }, "prioritizeCriticalApplications": { "message": "Daj prioritet kritičnim aplikacijama" }, @@ -377,10 +395,10 @@ "message": "Odaberi koje su aplikacije najvažnije za tvoju organizaciju, a zatim dodijeli sigurnosne zadatke članovima kako bi se riješili rizici." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Pregledaj nove prijave" }, "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "message": "Istaknuli smo rizične stavke za nove aplikacije pohranjene u administratorskoj konzoli koje imaju slabe, otkrivene ili ponovno korištene lozinke." }, "clickIconToMarkAppAsCritical": { "message": "Klikni ikonu zvjezdice za označavanje aplikacije kao kritične" @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Sljedeća naplata" }, + "nextChargeHeader": { + "message": "Sljedeća naplata" + }, + "plan": { + "message": "Paket" + }, "details": { "message": "Detalji" }, + "discount": { + "message": "popust" + }, "downloadLicense": { "message": "Preuzmi licencu" }, @@ -4440,7 +4467,31 @@ "message": "Ažuriraj preglednik" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Generiranje tvoje pristupne inteligencije..." + }, + "fetchingMemberData": { + "message": "Dohvaćanje podataka o članu…" + }, + "analyzingPasswordHealth": { + "message": "Analiziranje zdravlja lozinke…" + }, + "calculatingRiskScores": { + "message": "Izračun ocjene rizika…" + }, + "generatingReportData": { + "message": "Generiranje izvješća…" + }, + "savingReport": { + "message": "Spremanje izvještaja…" + }, + "compilingInsights": { + "message": "Sastavljanje uvida…" + }, + "loadingProgress": { + "message": "Učitavanje napretka" + }, + "thisMightTakeFewMinutes": { + "message": "Ovo može potrajati nekoliko minuta." }, "riskInsightsRunReport": { "message": "Pokreni izvješće" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Kako uključiti automatsku potvrdu korisnika" }, - "autoConfirmStep1": { - "message": "Otvori svoje Bitwarden proširenje." + "autoConfirmExtension1": { + "message": "Otvori svoje Bitwarden proširenje" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Odaberi", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Uključi.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Uključi", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Uspješno otvoreno Bitwarden proširenje za preglednik. Sada možeš aktivirati postavku automatske potvrde korisnika." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Prijava uspješna!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Dodijeli pristup zbirki" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Započni besplatno probno razdoblje za Families" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Postavi metodu otključavanja za promjenu radnje nakon isteka vremenskog ograničenja trezora." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Pravila tvrtke primijenjena su na tvoje mogućnosti vremenskog isteka" + }, + "vaultTimeoutTooLarge": { + "message": "Istek vremenskog ograničenja tvojeg trezora premašuje ograničenje koja je postavila tvoja organizacija." + }, + "neverLockWarning": { + "message": "Sigurni želiš koristiti opciju „Nikad”? Postavljanjem opcije zaključavanja na „Nikad” ključ za šifriranje tvojeg trezora sprema se na tvoj uređaj. Pri korištenju ove opcije osiguraj da je tvoj uređaj pravilno zaštićen." + }, + "sessionTimeoutSettingsAction": { + "message": "Radnja kod isteka" + }, + "sessionTimeoutHeader": { + "message": "Istek sesije" + }, + "appearance": { + "message": "Izgled" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Istek vremenskog ograničenja premašuje ograničenje koje je postavila tvoja organizacija: najviše $HOURS$:$MINUTES$.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index 0337a402522..d9bbd09b0f6 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Felülvizsgálatot igénylő kérelmek" }, + "newApplicationsCardTitle": { + "message": "Új alkalmazások felülvizsgálata" + }, "newApplicationsWithCount": { "message": "$COUNT$ új alkalmazás", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Jelenleg nincs új falkalmazás felülvizsgálatra." }, + "organizationHasItemsSavedForApplications": { + "message": "A szervezet $COUNT$ alkalmazáshoz mentett elemeket tartalmaz.", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Tekintsük át az alkalmazásokat, hogy biztosítsuk a szervezet biztonsága szempontjából legkritikusabb elemeket." + }, + "reviewApplications": { + "message": "Alkalmazások áttekintése" + }, "prioritizeCriticalApplications": { "message": "Kritikus alkalmazások rangsorolása" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Következő terhelés" }, + "nextChargeHeader": { + "message": "Következő terhelés" + }, + "plan": { + "message": "Csomag" + }, "details": { "message": "Részletek" }, + "discount": { + "message": "kedvezmény" + }, "downloadLicense": { "message": "Licensz letöltése" }, @@ -4440,7 +4467,31 @@ "message": "Böngésző frissítése" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Hozzáférési intelligencia generálása..." + }, + "fetchingMemberData": { + "message": "Tagi adatok lekérése..." + }, + "analyzingPasswordHealth": { + "message": "A jelszó állapot elemzése..." + }, + "calculatingRiskScores": { + "message": "Kockázati pontszámok kiszámítása..." + }, + "generatingReportData": { + "message": "Jelentés adatok generálása..." + }, + "savingReport": { + "message": "Jelentés mentése..." + }, + "compilingInsights": { + "message": "Betekintések összeállítása..." + }, + "loadingProgress": { + "message": "Feldolgozás betöltése" + }, + "thisMightTakeFewMinutes": { + "message": "Ez eltarthat pár percig." }, "riskInsightsRunReport": { "message": "Jelentés futtatása" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Hogyan kapcsolható be az automatikus felhasználó megerősítés" }, - "autoConfirmStep1": { + "autoConfirmExtension1": { "message": "Nyissuk meg a Bitwarden bővítményt." }, - "autoConfirmStep2a": { - "message": "Kijelölés", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Kiválasztás", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Bekapcsolás.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Bekapcsolás", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Sikeresen megnyitásra került a Bitwarden böngésző bővítmény. Most már aktiválhatjuk az automatikus felhasználó megerősítés beállítást." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Megtörtént a bejelentkezés." }, - "beta": { - "message": "Béta" - }, "assignCollectionAccess": { "message": "Gyűjtemény elérés hozzárendelése" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Az ingyenes Családok próbaverzió elindítása" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "A vállalkozáspolitikai követelményeket alkalmazásra kerültek az időkifutási opciókra." + }, + "vaultTimeoutTooLarge": { + "message": "A széf időkorlátja túllépi a szervezet által beállított korlátozást." + }, + "neverLockWarning": { + "message": "Biztosan szeretnénk használni a \"Soha\" opciót? A zárolási opciók \"Soha\" értékre állítása a széf titkosítási kulcsát az eszközön tárolja. Ennek az opciónak a használatakor célszerű az eszköz megfelelő védettségét biztosítani." + }, + "sessionTimeoutSettingsAction": { + "message": "Időkifutási művelet" + }, + "sessionTimeoutHeader": { + "message": "Munkamenet időkifutás" + }, + "appearance": { + "message": "Megjelenés" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Az időkifutás meghaladja a szervezet által beállított korlátozást: $HOURS$ óra és $MINUTES$ perc maximum.", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Nincsenek veszélyben levő kritikus alkalmazások." + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Biztos folytatni szeretnénk?" } } diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 0cab95e4de5..ad9d2405adb 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplikasi yang perlu ditinjau" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ aplikasi baru", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritaskan aplikasi penting" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Tagihan Berikutnya" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detail" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Unduh Lisensi" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Jalankan laporan" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { - "message": "Pilih", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Select", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Aktifkan.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Ekstensi peramban Bitwarden berhasil dibuka. Anda sekarang dapat mengaktifkan pengaturan konfirmasi pengguna otomatis." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index 8a9eac612b4..fa3146996d7 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applicazioni in attesa di revisione" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ nuove applicazioni", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Priorità alle applicazioni critiche" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Prossimo addebito" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Dettagli" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Scarica licenza" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Avvia report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Accesso effettuato!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assegna accesso alla raccolta" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index 6d56564c0c2..a70b821a1fc 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "次回の請求" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "詳細" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "ライセンスのダウンロード" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "ログインしました!" }, - "beta": { - "message": "ベータ" - }, "assignCollectionAccess": { "message": "コレクションへのアクセスを割り当て" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 43e46ef6d6b..973dcc2951b 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index e7051dee661..47df4826851 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index b18ca2a2cc4..17aaa51f261 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "ಮುಂದಿನ ಶುಲ್ಕ" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "ವಿವರಗಳು" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "ಪರವಾನಗಿ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index dbc9b219d05..f831e76b1eb 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "다음 지불" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "세부사항" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "라이선스 다운로드" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index d85ffcb3784..b35e704eb21 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Lietotnes, kuras ir nepieciešams izskatīt" }, + "newApplicationsCardTitle": { + "message": "Pārskatīt jaunās lietotnes" + }, "newApplicationsWithCount": { "message": "$COUNT$ jaunas lietotnes", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Pašreiz nav jaunu izskatāmu lietotņu" }, + "organizationHasItemsSavedForApplications": { + "message": "Tavā apvienībā ir saglabāti vienumi $COUNT$ lietotnēm", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Pārskati lietotnes, lai pastiprinātu drošību vienumiem, kuri ir visbūtiskākie apvienības drošībai" + }, + "reviewApplications": { + "message": "Pārskatīt lietotnes" + }, "prioritizeCriticalApplications": { "message": "Paaugstināt būtisko lietotņu svarīgumu" }, @@ -377,10 +395,10 @@ "message": "Jāatlasa, kuras lietontes apvienībai ir visbūtiskākās, tad jāpiešķir drošības uzdevumi dalībniekiem risku novēršanai." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Pārskatīt jaunās lietotnes" }, "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "message": "Mēs izcēlām riskam pakļautos vienumus jaunām lietotnēm, kas tiek glabātas pārvaldības konsolē un kurās ir vājas, atklātas vai atkārtoti izmantotas paroles." }, "clickIconToMarkAppAsCritical": { "message": "Jāklikšķina uz zvaigznes, lai atzīmētu lietotni kā būtisku" @@ -1218,7 +1236,7 @@ "message": "Pielikums izdzēsts" }, "deleteAttachmentConfirmation": { - "message": "Vai tiešām vēlaties dzēst šo pielikumu?" + "message": "Vai tiešām izdzēst šo pielikumu?" }, "attachmentSaved": { "message": "Pielikums tika saglabāts." @@ -1291,7 +1309,7 @@ "message": "Vienumi pārvietoti" }, "overwritePasswordConfirmation": { - "message": "Vai tiešām vēlaties pārrakstīt pašreizējo paroli?" + "message": "Vai tiešām pārrakstīt pašreizējo paroli?" }, "editedFolder": { "message": "Mape labota" @@ -1300,7 +1318,7 @@ "message": "Mape pievienota" }, "deleteFolderConfirmation": { - "message": "Vai tiešām vēlaties izdzēst šo mapi?" + "message": "Vai tiešām izdzēst šo mapi?" }, "deletedFolder": { "message": "Mape izdzēsta" @@ -1910,7 +1928,7 @@ } }, "deleteSelectedConfirmation": { - "message": "Vai tiešām vēlaties turpināt?" + "message": "Vai tiešām turpināt?" }, "moveSelectedItemsDesc": { "message": "Jāizvelas mape, kurā pievienot $COUNT$ atlasīto(s) vienumu(s).", @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Nākamais maksājums" }, + "nextChargeHeader": { + "message": "Nākamais maksājums" + }, + "plan": { + "message": "Plāns" + }, "details": { "message": "Izklāsts" }, + "discount": { + "message": "atlaide" + }, "downloadLicense": { "message": "Lejupielādēt licenci" }, @@ -3658,10 +3685,10 @@ "message": "Labot kopu" }, "deleteGroupConfirmation": { - "message": "Vai tiešām vēlaties dzēst šo kopu?" + "message": "Vai tiešām izdzēst šo kopu?" }, "deleteMultipleGroupsConfirmation": { - "message": "Vai tiešām vēlaties dzēst šādu $QUANTITY$ grupu(-as)?", + "message": "Vai tiešām izdzēst šo (šīs) $QUANTITY$ kopu(as)?", "placeholders": { "quantity": { "content": "$1", @@ -4440,7 +4467,31 @@ "message": "Atjaunināt pārlūku" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Izveido informāciju par Tavu piekļuvi…" + }, + "fetchingMemberData": { + "message": "Iegūst dalībnieku datus…" + }, + "analyzingPasswordHealth": { + "message": "Izvērtē paroļu veselību…" + }, + "calculatingRiskScores": { + "message": "Aprēķina risku novērtējumu…" + }, + "generatingReportData": { + "message": "Izveido atskaites datus…" + }, + "savingReport": { + "message": "Saglabā atskaiti…" + }, + "compilingInsights": { + "message": "Apkopo ieskatus…" + }, + "loadingProgress": { + "message": "Ielādē virzību" + }, + "thisMightTakeFewMinutes": { + "message": "Tas var aizņemt dažas minūtes." }, "riskInsightsRunReport": { "message": "Izveidot atskaiti" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Kā ieslēgt automātisku lietotōtāju apstiprināšanu" }, - "autoConfirmStep1": { - "message": "Jāatver Bitwarden paplašinājums." + "autoConfirmExtension1": { + "message": "Jāatver Bitwarden paplašinājums" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Jāatlasa", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": "“Ieslēgt”.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " “Ieslēgt”", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Bitwarden pārlūka paplašīnājums atvērts sekmīgi. Tagad var ieslēgt automātisko lietotāju apstiprināšanas iestatījumu." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Pieteicies." }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Piešķirt krājumu piekļuvi" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Uzsākt ģimenes plāna bezmaksas izmēģinājumu" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Jāuzstāda atslēgšanas veids, lai mainītu glabātavas noildzes darbību." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Noildzes iespējām tika piemērotas uzņēmējdarbības pamatnostādņu prasības" + }, + "vaultTimeoutTooLarge": { + "message": "Glabātavas noildze pārsniedz apvienības uzstādītos ierobežojumus." + }, + "neverLockWarning": { + "message": "Vai tiešām izmantot uzstādījumu \"Nekad\"? Uzstādot aizslēgšanas iespēju uz \"Nekad\", šifrēšanas atslēga tiek glabāta ierīcē. Ja šī iespēja tiek izmantota, jāpārliecinās, ka ierīce tiek pienācīgi aizsargāta." + }, + "sessionTimeoutSettingsAction": { + "message": "Noildzes darbība" + }, + "sessionTimeoutHeader": { + "message": "Sesijas noildze" + }, + "appearance": { + "message": "Izskats" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Noildze pārsniedz apvienības iestatīto ierobežojumu: ne vairāk kā $HOURS$ stunda(s) un $MINUTES$ minūte(s)", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Neviena būtiska lietotne nav atlasīta" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Vai tiešām turpināt?" } } diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 1d892bdaa60..624121be5d9 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next Charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "ലൈസൻസ് ഡൌൺലോഡ് ചെയ്യുക" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 5c65192e0b4..612ff8b8765 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index e7051dee661..47df4826851 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index c3f80105f40..b2fb413be8c 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Neste trekk" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detaljer" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Last ned lisens" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Innlogget!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 9da5012921d..8bd88ef4a57 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 4b68dfe4be6..8ac8cf24ca5 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aanvragen die herzien moeten worden" }, + "newApplicationsCardTitle": { + "message": "Nieuwe toepassingen beoordelen" + }, "newApplicationsWithCount": { "message": "$COUNT$ nieuwe applicaties", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Je organisatie heeft items opgeslagen voor $COUNT$ applicaties", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Beoordeel applicaties om de items die het meest belangrijk zijn voor de veiligheid van je organisatie te beveiligen" + }, + "reviewApplications": { + "message": "Applicaties beoordelen" + }, "prioritizeCriticalApplications": { "message": "Belangrijke applicaties prioriteren" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Volgende betaling" }, + "nextChargeHeader": { + "message": "Volgende betaling" + }, + "plan": { + "message": "Pakket" + }, "details": { "message": "Details" }, + "discount": { + "message": "korting" + }, "downloadLicense": { "message": "Licentie downloaden" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Je toegangsinlichtingen genereren..." }, + "fetchingMemberData": { + "message": "Ledengegevens ophalen..." + }, + "analyzingPasswordHealth": { + "message": "Wachtwoordgezondheid analyseren..." + }, + "calculatingRiskScores": { + "message": "Risicoscores berekenen..." + }, + "generatingReportData": { + "message": "Rapportgegevens genereren..." + }, + "savingReport": { + "message": "Rapport opslaan..." + }, + "compilingInsights": { + "message": "Inzichten compileren..." + }, + "loadingProgress": { + "message": "Voortgang laden" + }, + "thisMightTakeFewMinutes": { + "message": "Dit kan een paar minuten duren." + }, "riskInsightsRunReport": { "message": "Rapport uitvoeren" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Automatische bevestiging van gebruikers inschakelen" }, - "autoConfirmStep1": { - "message": "De Bitwarden-extensie openen." + "autoConfirmExtension1": { + "message": "Bitwarden-extensie openen" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Selecteer", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Inschakelen.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Inschakelen", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "De Bitwarden-browserextensie is succesvol geopend. Je kunt nu de automatische bevestiging van de gebruiker activeren." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Ingelogd!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Toegang collectie toewijzen" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start gratis Families-proefperiode" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Bedrijfsbeleidseisen zijn toegepast op je time-out instellingen" + }, + "vaultTimeoutTooLarge": { + "message": "Je kluis time-out is hoger dan het maximum van jouw organisatie." + }, + "neverLockWarning": { + "message": "Weet je zeker dat je de optie \"Nooit\" wilt gebruiken? De vergrendelingsoptie \"Nooit\" bewaart de sleutel van je kluis op je apparaat. Als je deze optie gebruikt, moet je ervoor zorgen dat je je apparaat naar behoren beschermt." + }, + "sessionTimeoutSettingsAction": { + "message": "Time-out actie" + }, + "sessionTimeoutHeader": { + "message": "Sessietime-out" + }, + "appearance": { + "message": "Uiterlijk" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Time-out overschrijdt de beperking van je organisatie: $HOURS$ uur en $MINUTES$ minu(u) t(en) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Geen belangrijke applicaties geselecteerd" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Weet je zeker dat je wilt doorgaan?" } } diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 24845833d22..b6ec66bf695 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index e7051dee661..47df4826851 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 4d450fa7543..9750a2a7df7 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Następna opłata" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Szczegóły" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Pobierz licencję" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Zalogowano!" }, - "beta": { - "message": "Wersja beta" - }, "assignCollectionAccess": { "message": "Przypisz dostęp do kolekcji" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 7d4edcecf5f..4ef989dcd75 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplicativos que precisam de revisão" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ aplicativos novos", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Priorizar aplicativos críticos" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Próxima cobrança" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detalhes" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Baixar licença" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Executar relatório" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Conectado!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Atribuir acesso à coleção" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 09e346b494f..45b511a857f 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplicações que necessitam de análise" }, + "newApplicationsCardTitle": { + "message": "Rever novas aplicações" + }, "newApplicationsWithCount": { "message": "$COUNT$ novas aplicações", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Não há novas aplicações para rever neste momento" }, + "organizationHasItemsSavedForApplications": { + "message": "A sua organização tem itens guardados para $COUNT$ aplicações", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Reveja as aplicações para proteger os itens mais críticos para a segurança da sua organização" + }, + "reviewApplications": { + "message": "Rever aplicações" + }, "prioritizeCriticalApplications": { "message": "Dê prioridade a aplicações críticas" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Próxima cobrança" }, + "nextChargeHeader": { + "message": "Próxima cobrança" + }, + "plan": { + "message": "Plano" + }, "details": { "message": "Detalhes" }, + "discount": { + "message": "desconto" + }, "downloadLicense": { "message": "Transferir licença" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "A gerar a sua Inteligência de Acesso..." }, + "fetchingMemberData": { + "message": "A obter dados dos membros..." + }, + "analyzingPasswordHealth": { + "message": "A analisar a segurança da palavra-passe..." + }, + "calculatingRiskScores": { + "message": "A calcular pontuações de risco..." + }, + "generatingReportData": { + "message": "A gerar dados do relatório..." + }, + "savingReport": { + "message": "A guardar relatório..." + }, + "compilingInsights": { + "message": "A compilar insights..." + }, + "loadingProgress": { + "message": "A carregar progresso" + }, + "thisMightTakeFewMinutes": { + "message": "Isto pode demorar alguns minutos." + }, "riskInsightsRunReport": { "message": "Executar relatório" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Como ativar a confirmação automática do utilizador" }, - "autoConfirmStep1": { - "message": "Abra a sua extensão Bitwarden." + "autoConfirmExtension1": { + "message": "Abra a sua extensão Bitwarden" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Selecione", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Ativar.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Ativar", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "A extensão do navegador Bitwarden foi aberta com sucesso. Agora pode ativar a configuração de confirmação automática do utilizador." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Sessão iniciada!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Atribuir acesso à coleção" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Comece o teste gratuito do plano Familiar" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Configure um método de desbloqueio para alterar a ação de tempo limite do seu cofre." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Os requisitos da política empresarial foram aplicados às suas opções de tempo limite" + }, + "vaultTimeoutTooLarge": { + "message": "O tempo limite do seu cofre excede as restrições definidas pela sua organização." + }, + "neverLockWarning": { + "message": "Tem a certeza de que deseja utilizar a opção \"Nunca\"? Ao definir as opções de bloqueio para \"Nunca\" armazena a chave de encriptação do seu cofre no seu dispositivo. Se utilizar esta opção deve assegurar-se de que mantém o seu dispositivo devidamente protegido." + }, + "sessionTimeoutSettingsAction": { + "message": "Ação de tempo limite" + }, + "sessionTimeoutHeader": { + "message": "Tempo limite da sessão" + }, + "appearance": { + "message": "Aspeto" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "O tempo limite excede a restrição definida pela sua organização: $HOURS$ hora(s) e $MINUTES$ minuto(s) no máximo", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Não foram selecionadas aplicações críticas" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Tem a certeza de que deseja continuar?" } } diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 3a3e8846c66..e683e0a5f10 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Următoarea plată" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detalii" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Descărcare licență" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 31a1729cef8..9b5a9399491 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Приложения, требующие проверки" }, + "newApplicationsCardTitle": { + "message": "Обзор новых приложений" + }, "newApplicationsWithCount": { "message": "$COUNT$ новых приложений", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "На данный момент новых приложений для рассмотрения нет" }, + "organizationHasItemsSavedForApplications": { + "message": "В вашей организации есть элементы, сохраненные для $COUNT$ приложений", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Просмотрите приложения, чтобы защитить элементы, наиболее критичные для безопасности вашей организации" + }, + "reviewApplications": { + "message": "Обзор приложений" + }, "prioritizeCriticalApplications": { "message": "Приоритет критичных приложений" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Следующий платеж" }, + "nextChargeHeader": { + "message": "Следующий платеж" + }, + "plan": { + "message": "План" + }, "details": { "message": "Подробности" }, + "discount": { + "message": "скидка" + }, "downloadLicense": { "message": "Загрузить лицензию" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Получение данных о пользователях..." + }, + "analyzingPasswordHealth": { + "message": "Анализ здоровья пароля..." + }, + "calculatingRiskScores": { + "message": "Расчет показателей риска..." + }, + "generatingReportData": { + "message": "Генерация данных отчета..." + }, + "savingReport": { + "message": "Сохранение отчета..." + }, + "compilingInsights": { + "message": "Компиляция информации..." + }, + "loadingProgress": { + "message": "Прогресс загрузки" + }, + "thisMightTakeFewMinutes": { + "message": "Это может занять несколько минут." + }, "riskInsightsRunReport": { "message": "Запустить отчет" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Как включить автоматическое подтверждение пользователя" }, - "autoConfirmStep1": { - "message": "Откройте свое расширение Bitwarden." + "autoConfirmExtension1": { + "message": "Откройте свое расширение Bitwarden" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Выбрать", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Включить.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Включить", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Успешно открыто расширение браузера Bitwarden. Теперь вы можете активировать автоматическое подтверждение пользователя." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Выполнен вход!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Назначить доступ к коллекциям" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Начать пробную версию Families" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Настройте способ разблокировки для изменения действия по тайм-ауту хранилища." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "К настройкам тайм-аута были применены требования корпоративной политики" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "Критичные приложения не выбраны" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Вы действительно хотите продолжить?" } } diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 8d40ec3d93c..0fdc35fb8dc 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 9dba4eb22b2..fdcaa412db9 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -21,13 +21,13 @@ "message": "Ohrozenie hesla" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Na úpravu tejto položky nemáte oprávnenie" }, "reviewAtRiskPasswords": { "message": "Skontrolujte ohrozené heslá (slabé, odhalené, alebo opätovne použité) naprieč aplikáciami. Vyberte najkritickejšie aplikácie a priorizujte vaším používateľom bezpečnostné opatrenia ohľadom ohrozených hesiel." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "Prehľad ohrozených prihlasovacích mien" }, "dataLastUpdated": { "message": "Posledná aktualizácia dát: $DATE$", @@ -242,7 +242,7 @@ "message": "Aplikácie označené ako kritické" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ aplikácií označených ako kritické", "placeholders": { "count": { "content": "$1", @@ -275,10 +275,10 @@ "message": "Členovia s prístupom k ohrozeným položkám kritických aplikácii" }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Členovia s ohrozenými heslami" }, "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "message": "Členovia dostanú notifikáciu aby vyriešili problémy s ohrozeným heslom prostredníctvom rozšírenia pre prehliadače." }, "membersAtRiskCount": { "message": "$COUNT$ ohrozených členov", @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Aplikácie vyžadujú kontrolu" }, + "newApplicationsCardTitle": { + "message": "Skontrolovať nové aplikácie" + }, "newApplicationsWithCount": { "message": "$COUNT$ nových aplikácií", "placeholders": { @@ -370,11 +373,26 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Uprednostniť kritické aplikácie" }, "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "message": "Vyberte ktoré aplikácie sú pre vašu organizáciu najkritickejšie, potom prideľte členom bezpečnostné úlohy pre vyriešenie ohrozených hesiel." }, "reviewNewApplications": { "message": "Review new applications" @@ -383,7 +401,7 @@ "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Aplikáciu označíte za kritickú kliknutím na ikonu hviezdičky" }, "markAsCriticalPlaceholder": { "message": "Funkcia označovania za kritické bude implementovaná v budúcej aktualizácii" @@ -863,7 +881,7 @@ "message": "Obľúbené" }, "taskSummary": { - "message": "Task summary" + "message": "Zhrnutie úloh" }, "types": { "message": "Typy" @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Ďalšia platba" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Podrobnosti" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Stiahnuť licenciu" }, @@ -4440,7 +4467,31 @@ "message": "Aktualizovať prehliadač" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "Generuje sa prehľad o prístupe..." + }, + "fetchingMemberData": { + "message": "Sťahujú sa dáta o členoch..." + }, + "analyzingPasswordHealth": { + "message": "Analyzuje sa odolnosť hesiel..." + }, + "calculatingRiskScores": { + "message": "Vypočítava sa úroveň ohrozenia..." + }, + "generatingReportData": { + "message": "Generujú sa dáta reportu..." + }, + "savingReport": { + "message": "Ukladá sa report..." + }, + "compilingInsights": { + "message": "Kompiluje sa prehľad..." + }, + "loadingProgress": { + "message": "Priebeh načítania" + }, + "thisMightTakeFewMinutes": { + "message": "Môže to trvať niekoľko minút." }, "riskInsightsRunReport": { "message": "Generovať report" @@ -5460,7 +5511,7 @@ "description": "Displayed under the limit views field on Send" }, "limitSendViewsCount": { - "message": "Zostáva $ACCESSCOUNT$ zobrazení", + "message": "Zostávajúce zobrazenia: $ACCESSCOUNT$", "description": "Displayed under the limit views field on Send", "placeholders": { "accessCount": { @@ -5503,11 +5554,11 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deletedSend": { - "message": "Send zmazaný", + "message": "Send bol odstránený", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSend": { - "message": "Zmazať Send", + "message": "Odstrániť Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "deleteSendPermanentConfirmation": { @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Ako zapnúť automatické potvrdzovanie používateľov" }, - "autoConfirmStep1": { - "message": "Otvoriť rozšírenie Bitwarden." + "autoConfirmExtension1": { + "message": "Otvoriť rozšírenie Bitwarden" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Vybrať", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Zapnúť.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Zapnúť", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Rozšírenie Bitwarden pre prehliadače úspešne otvorene. Teraz môžete v nastaveniach zapnúť automatické potvrdzovanie používateľov." @@ -5836,7 +5887,7 @@ "message": "Vyžaduje sa pravidlo jednej organizácie. " }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Pre zapnutie tejto automatizácie musia všetci členovia patriť len do tejto organizácie." }, "autoConfirmSingleOrgExemption": { "message": "Pravidlo jednej organizácie sa rozšíri na všetky roly. " @@ -6569,10 +6620,10 @@ "message": "Vaše hlavné heslo nespĺňa jednu alebo viacero podmienok vašej organizácie. Ak chcete získať prístup k trezoru, musíte teraz aktualizovať svoje hlavné heslo. Pokračovaním sa odhlásite z aktuálnej relácie a budete sa musieť znova prihlásiť. Aktívne relácie na iných zariadeniach môžu zostať aktívne až jednu hodinu." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "Automatické prihlásenie prostredníctvom SSO" }, "automaticAppLoginWithSSODesc": { - "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." + "message": "Rozšírte bezpečnosť a pohodlie jednotného prihlásenia (SSO) na nespravované aplikácie. Keď používatelia spustia aplikáciu od vášho poskytovateľa identít, ich prihlasovacie údaje sa automaticky vyplnia a odošlú, čím sa umožní bezpečná cesta jedným kliknutím od poskytovateľa identít do aplikácie." }, "automaticAppLoginIdpHostLabel": { "message": "Identity provider host" @@ -7348,7 +7399,7 @@ "message": "Prístup zamietnutý. Nemáte oprávnenie na zobrazenie tejto stránky." }, "noPageAccess": { - "message": "You do not have access to this page" + "message": "Na túto stránku nemáte prístup" }, "masterPassword": { "message": "Hlavné heslo" @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Prihlásený!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Udeliť prístup k zbierke" }, @@ -9809,7 +9857,7 @@ "message": "Priradiť úlohy" }, "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "message": "Pre riadené riešenie problémov prideľte úlohy členom" }, "assignToCollections": { "message": "Prideliť k zbierkam" @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Začať bezplatnú skúšku pre predplatné Rodiny" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 771f16e53cf..709baa2e36c 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Podrobnosti" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Prenesi licenco" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 753ea33c2c1..876669827c8 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Sledeće Plaćanje" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detalji" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index e17983809ae..dd41032a031 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -21,13 +21,13 @@ "message": "Ризик од лозинке" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Немате дозволу да уређујете ову ставку" }, "reviewAtRiskPasswords": { "message": "Прегледај ризичне лозинке (слабе, изложене или поново коришћене) у апликацијама. Изабери своје најкритичније апликације да би дао приоритет безбедносним радњама како би твоји корисници адресирали ризичне лозинке." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "Прегледајте ризичне пријаве" }, "dataLastUpdated": { "message": "Подаци су последњи пут ажурирани: $DATE$", @@ -179,7 +179,7 @@ } }, "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", + "message": "Није пронађена ниједна апликација за $ORG NAME$", "placeholders": { "org name": { "content": "$1", @@ -188,31 +188,31 @@ } }, "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "message": "Увезите податке за пријаву своје организације да бисте почели да надгледате безбедносне ризике акредитива. Када увезете, добијате:" }, "benefit1Title": { - "message": "Prioritize risks" + "message": "Одредите приоритете ризика" }, "benefit1Description": { - "message": "Focus on applications that matter the most" + "message": "Фокусирајте се на апликације које су најважније" }, "benefit2Title": { - "message": "Guide remediation" + "message": "Водич за санацију" }, "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "message": "Доделите вођене задатке члановима изложеним ризику да ротирају акредитиве у ризику" }, "benefit3Title": { - "message": "Monitor progress" + "message": "Праћење напретка" }, "benefit3Description": { - "message": "Track changes over time to show security improvements" + "message": "Пратите промене током времена да бисте показали безбедносна побољшања" }, "noReportRunTitle": { - "message": "Run your first report to see applications" + "message": "Покрените свој први извештај да бисте видели апликације" }, "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "message": "Направите извештај о увиду у ризик да бисте анализирали апликације ваше организације и идентификовали ризичне лозинке на које треба обратити пажњу. Покретање вашег првог извештаја ће:" }, "noCriticalApplicationsTitle": { "message": "Нисте означили ниједну апликацију као критичну" @@ -242,7 +242,7 @@ "message": "Апликације означене као критичне" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "Апликације означене као критичне: $COUNT$", "placeholders": { "count": { "content": "$1", @@ -275,10 +275,10 @@ "message": "Чланови са приступом за угрожене ставке критичних апликација" }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "Чланови са ризичним лозинкама" }, "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "message": "Чланови ће добити обавештење за решавање ризичних пријава путем екстензије претраживача." }, "membersAtRiskCount": { "message": "Угрожени чланови: $COUNT$", @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Апликације које треба прегледати" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -365,40 +368,55 @@ "message": "Прегледај сада" }, "allCaughtUp": { - "message": "All caught up!" + "message": "Сви ухваћени!" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "Тренутно нема нових апликација за преглед" + }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" }, "prioritizeCriticalApplications": { "message": "Дајте приоритет критичним апликацијама" }, "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "message": "Изаберите које су апликације најкритичније за вашу организацију, а затим доделите безбедносне задатке члановима да бисте решили ризике." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "Прегледајте нове апликације" }, "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "message": "Истакли смо ризичне ставке за нове апликације ускладиштене у Админ конзоли које имају слабе, откривене или поново коришћене лозинке." }, "clickIconToMarkAppAsCritical": { - "message": "Click the star icon to mark an app as critical" + "message": "Кликните на икону звездице да бисте означили апликацију као критичну" }, "markAsCriticalPlaceholder": { "message": "Означи као критичну функционалност ће бити имплементирана у будућем ажурирању" }, "applicationReviewSaved": { - "message": "Application review saved" + "message": "Преглед апликације је сачуван" }, "newApplicationsReviewed": { - "message": "New applications reviewed" + "message": "Нове апликације су прегледане" }, "errorSavingReviewStatus": { - "message": "Error saving review status" + "message": "Грешка при чувању статуса прегледа" }, "pleaseTryAgain": { - "message": "Please try again" + "message": "Покушајте поново" }, "unmarkAsCritical": { "message": "Уклони као критично" @@ -863,7 +881,7 @@ "message": "Омиљени" }, "taskSummary": { - "message": "Task summary" + "message": "Резиме задатка" }, "types": { "message": "Врсте" @@ -1390,7 +1408,7 @@ "message": "Употребити једнократну пријаву" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Ваша организација захтева јединствену пријаву." }, "welcomeBack": { "message": "Добродошли назад" @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Следеће пуњење" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Детаљи" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Преузимање лиценце" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Преузимање података о члановима..." + }, + "analyzingPasswordHealth": { + "message": "Анализа здравља лозинки..." + }, + "calculatingRiskScores": { + "message": "Израчунавање резултата ризика..." + }, + "generatingReportData": { + "message": "Генерисање података извештаја..." + }, + "savingReport": { + "message": "Чување извештаја..." + }, + "compilingInsights": { + "message": "Састављање увида..." + }, + "loadingProgress": { + "message": "Учитавање напретка" + }, + "thisMightTakeFewMinutes": { + "message": "Ово може потрајати неколико минута." + }, "riskInsightsRunReport": { "message": "Покрените извештај" }, @@ -5792,63 +5843,63 @@ "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Learn more about the credential lifecycle.'" }, "availableNow": { - "message": "Available now" + "message": "Доступно сада" }, "autoConfirm": { - "message": "Automatic confirmation of new users" + "message": "Аутоматска потврда нових корисника" }, "autoConfirmDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked.", + "message": "Нови корисници позвани у организацију биће аутоматски потврђени када се администраторски уређај откључа.", "description": "This is the description of the policy as it appears in the 'Policies' page" }, "howToTurnOnAutoConfirm": { - "message": "How to turn on automatic user confirmation" + "message": "Како укључити аутоматску потврду корисника" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Отворити Bitwarden екстензију" }, - "autoConfirmStep2a": { - "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Изабери", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Укључи", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { - "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." + "message": "Успешно је отворена екстензија прегледача Bitwarden-а. Сада можете активирати поставку аутоматске потврде корисника." }, "autoConfirmPolicyEditDescription": { - "message": "New users invited to the organization will be automatically confirmed when an admin’s device is unlocked. Before turning on this policy, please review and agree to the following: ", + "message": "Нови корисници позвани у организацију биће аутоматски потврђени када се администраторски уређај откључа. Пре него што укључите ову политику, прегледајте и прихватите следеће: ", "description": "This is the description of the policy as it appears inside the policy edit dialog" }, "autoConfirmAcceptSecurityRiskTitle": { - "message": "Potential security risk. " + "message": "Потенцијални безбедносни ризик. " }, "autoConfirmAcceptSecurityRiskDescription": { - "message": "Automatic user confirmation could pose a security risk to your organization’s data." + "message": "Аутоматска потврда корисника може представљати безбедносни ризик за податке ваше организације." }, "autoConfirmAcceptSecurityRiskLearnMore": { - "message": "Learn about the risks", + "message": "Сазнајте више о ризицима", "description": "The is the link copy for the first check box option in the edit policy dialog" }, "autoConfirmSingleOrgRequired": { - "message": "Single organization policy required. " + "message": "Смернице за јединствену организацију су потребне. " }, "autoConfirmSingleOrgRequiredDesc": { - "message": "All members must only belong to this organization to activate this automation." + "message": "Сви чланови морају припадати само овој организацији да би активирали ову аутоматизацију." }, "autoConfirmSingleOrgExemption": { - "message": "Single organization policy will extend to all roles. " + "message": "Политика једне организације прошириће се на све улоге. " }, "autoConfirmNoEmergencyAccess": { - "message": "No emergency access. " + "message": "Нема хитан приступ. " }, "autoConfirmNoEmergencyAccessDescription": { - "message": "Emergency Access will be removed." + "message": "Хитан приступ ће бити уклоњен." }, "autoConfirmCheckBoxLabel": { - "message": "I accept these risks and policy updates" + "message": "Прихватам ове ризике и ажурирања политика" }, "personalOwnership": { "message": "Лично власништво" @@ -5903,16 +5954,16 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "uriMatchDetectionPolicy": { - "message": "Default URI match detection" + "message": "Стандардно налажење УРЛ" }, "uriMatchDetectionPolicyDesc": { - "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + "message": "Одредите када се предлажу пријаве за ауто-попуњавање. Администратори и власници су изузети од ове политике." }, "uriMatchDetectionOptionsLabel": { - "message": "Default URI match detection" + "message": "Стандардно налажење УРЛ" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Изаберите важећу опцију откривања налажења УРЛ-а.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -6569,7 +6620,7 @@ "message": "Ваша главна лозинка не испуњава једну или више смерница ваше организације. Да бисте приступили сефу, морате одмах да ажурирате главну лозинку. Ако наставите, одјавићете се са ваше тренутне сесије, што захтева да се поново пријавите. Активне сесије на другим уређајима могу да остану активне до један сат." }, "automaticAppLoginWithSSO": { - "message": "Automatic login with SSO" + "message": "Аутоматско пријављивање са ССО" }, "automaticAppLoginWithSSODesc": { "message": "Extend SSO security and convenience to unmanaged apps. When users launch an app from your identity provider, their login details are automatically filled and submitted, creating a one-click, secure flow from the identity provider to the app." @@ -6584,31 +6635,31 @@ "message": "Ваша организација је ажурирала опције дешифровања. Поставите главну лозинку за приступ вашем сефу." }, "sessionTimeoutPolicyTitle": { - "message": "Session timeout" + "message": "Истек сесије" }, "sessionTimeoutPolicyDescription": { - "message": "Set a maximum session timeout for all members except owners." + "message": "Подесите максимално временско ограничење сесије за све чланове осим власника." }, "maximumAllowedTimeout": { - "message": "Maximum allowed timeout" + "message": "Максимално временско ограничење" }, "maximumAllowedTimeoutRequired": { - "message": "Maximum allowed timeout is required." + "message": "Максимално временско ограничење је обавезно." }, "sessionTimeoutPolicyInvalidTime": { - "message": "Time is invalid. Change at least one value." + "message": "Време је неважеће. Промените бар једну вредност." }, "sessionTimeoutAction": { - "message": "Session timeout action" + "message": "Акција на истек сесије" }, "immediately": { - "message": "Immediately" + "message": "Одмах" }, "onSystemLock": { - "message": "On system lock" + "message": "На закључавање система" }, "onAppRestart": { - "message": "On app restart" + "message": "На поновно покретање" }, "hours": { "message": "Сати/а" @@ -6617,19 +6668,19 @@ "message": "Минути/а" }, "sessionTimeoutConfirmationNeverTitle": { - "message": "Are you certain you want to allow a maximum timeout of \"Never\" for all members?" + "message": "Да ли сте сигурни да желите да дозволите максимално временско ограничење „Никад“ за све чланове?" }, "sessionTimeoutConfirmationNeverDescription": { - "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." + "message": "Ова опција ће сачувати кључеве за шифровање ваших чланова на њиховим уређајима. Ако одаберете ову опцију, уверите се да су њихови уређаји адекватно заштићени." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Сазнајте више о заштити уређаја" }, "sessionTimeoutConfirmationOnSystemLockTitle": { - "message": "\"System lock\" will only apply to the browser and desktop app" + "message": "„Закључавање система“ ће се примењивати само на прегледач и десктоп апликацију" }, "sessionTimeoutConfirmationOnSystemLockDescription": { - "message": "The mobile and web app will use \"on app restart\" as their maximum allowed timeout, since the option is not supported." + "message": "Мобилна и веб апликација ће користити „при поновном покретању апликације“ као максимално дозвољено временско ограничење, пошто опција није подржана." }, "vaultTimeoutPolicyInEffect": { "message": "Полиса ваше организације утиче на време истека сефа. Максимално дозвољено време истека је $HOURS$ сат(и) и $MINUTES$ minut(а)", @@ -7173,7 +7224,7 @@ "message": "Морате додати или основни УРЛ сервера или бар једно прилагођено окружење." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Везе морају да користе HTTPS." }, "apiUrl": { "message": "УРЛ АПИ Сервера" @@ -7348,7 +7399,7 @@ "message": "Одбијен приступ. Немате дозволу да видите ову страницу." }, "noPageAccess": { - "message": "You do not have access to this page" + "message": "Немате приступ овој страници" }, "masterPassword": { "message": "Главна Лозинка" @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Пријављено!" }, - "beta": { - "message": "Бета" - }, "assignCollectionAccess": { "message": "Додели приступ збирке" }, @@ -9809,7 +9857,7 @@ "message": "Додели задатке" }, "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "message": "Доделите задатке члановима за вођено решавање" }, "assignToCollections": { "message": "Додели колекцијама" @@ -11199,13 +11247,13 @@ "message": "Домен захтеван" }, "itemAddedToFavorites": { - "message": "Item added to favorites" + "message": "Ставка је додата у фаворите" }, "itemRemovedFromFavorites": { - "message": "Item removed from favorites" + "message": "Ставка је уклоњена из фаворите" }, "copyNote": { - "message": "Copy note" + "message": "Копирај белешку" }, "organizationNameMaxLength": { "message": "Име организације не може прећи 50 знакова." @@ -12041,39 +12089,79 @@ "message": "View business plans" }, "updateEncryptionSettings": { - "message": "Update encryption settings" + "message": "Ажурирајте поставке за шифровање" }, "updateYourEncryptionSettings": { - "message": "Update your encryption settings" + "message": "Ажурирајте своје поставке за шифровање" }, "updateSettings": { - "message": "Update settings" + "message": "Ажурирај подешавања" }, "algorithm": { - "message": "Algorithm" + "message": "Алгоритам" }, "encryptionKeySettingsHowShouldWeEncryptYourData": { - "message": "Choose how Bitwarden should encrypt your vault data. All options are secure, but stronger methods offer better protection - especially against brute-force attacks. Bitwarden recommends the default setting for most users." + "message": "Изаберите како Bitwarden треба да шифрује ваше податке у сефу. Све опције су безбедне, али јаче методе нуде бољу заштиту - посебно од напада грубом силом. Bitwarden препоручује подразумевану поставку за већину корисника." }, "encryptionKeySettingsIncreaseImproveSecurity": { - "message": "Increasing the values above the default will improve security, but your vault may take longer to unlock as a result." + "message": "Повећање вредности изнад подразумеваних ће побољшати безбедност, али због тога ће вашем сефу бити потребно више времена да се откључа." }, "encryptionKeySettingsAlgorithmPopoverTitle": { - "message": "About encryption algorithms" + "message": "О алгоритмима за шифровање" }, "encryptionKeySettingsAlgorithmPopoverPBKDF2": { - "message": "PBKDF2-SHA256 is a well-tested encryption method that balances security and performance. Good for all users." + "message": "PBKDF2-SHA256 је добро тестиран метод шифровања који балансира безбедност и перформансе. Добро за све кориснике." }, "encryptionKeySettingsAlgorithmPopoverArgon2Id": { - "message": "Argon2id offers stronger protection against modern attacks. Best for advanced users with powerful devices." + "message": "Argon2id нуди јачу заштиту од модерних напада. Најбоље за напредне кориснике са моћним уређајима." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "ZIP/Поштански број" }, "cardNumberLabel": { - "message": "Card number" + "message": "Број картице" }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index d8a4d43946c..ca0547d7d95 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applikationer som behöver granskning" }, + "newApplicationsCardTitle": { + "message": "Granska nya applikationer" + }, "newApplicationsWithCount": { "message": "$COUNT$ nya applikationer", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Inga nya applikationer att granska just nu" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritera kritiska applikationer" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Nästa debitering" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Detaljer" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Hämta licens" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Hämtar medlemsdata..." + }, + "analyzingPasswordHealth": { + "message": "Analyserar lösenordshälsa..." + }, + "calculatingRiskScores": { + "message": "Beräknar riskpoäng..." + }, + "generatingReportData": { + "message": "Genererar rapportdata..." + }, + "savingReport": { + "message": "Sparar rapport..." + }, + "compilingInsights": { + "message": "Sammanställer insikter..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "Detta kan ta några minuter." + }, "riskInsightsRunReport": { "message": "Kör rapport" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Så här aktiverar du automatisk användarbekräftelse" }, - "autoConfirmStep1": { - "message": "Öppna ditt Bitwarden-tillägg." + "autoConfirmExtension1": { + "message": "Öppna ditt Bitwarden-tillägg" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Välj", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Slå på.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Slå på", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Inloggad!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Tilldela samlingsåtkomst" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Starta gratis testperiod för Families" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Konfigurera en upplåsningsmetod för att ändra tidsgränsåtgärden för valvet." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Företagets policykrav har tillämpats på dina tidsgränsalternativ" + }, + "vaultTimeoutTooLarge": { + "message": "Ditt valvs tidsgräns överskrider de begränsningar som fastställts av din organisation." + }, + "neverLockWarning": { + "message": "Är du säker på att du vill använda alternativet ”Aldrig”? Att ställa in låsningsalternativet till ”Aldrig” lagrar valvets krypteringsnyckel på din enhet. Om du använder det här alternativet bör du se till att du håller enheten ordentligt skyddad." + }, + "sessionTimeoutSettingsAction": { + "message": "Tidsgränsåtgärd" + }, + "sessionTimeoutHeader": { + "message": "Sessionstidsgräns" + }, + "appearance": { + "message": "Utseende" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Tidsgränsen överskrider den begränsning som din organisation har ställt in: $HOURS$ timmar och $MINUTES$ minut(er) maximalt", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index a5acacede66..bea4f3365bc 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "அடுத்த கட்டணம்" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "விவரங்கள்" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "லைசன்ஸ் கோப்பைப் பதிவிறக்கவும்" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "உள்நுழைந்துவிட்டீர்கள்!" }, - "beta": { - "message": "பீட்டா" - }, "assignCollectionAccess": { "message": "சேகரிப்பு அணுகலை ஒதுக்கவும்" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index e7051dee661..47df4826851 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index fd74c2d12a8..3e4797eb03f 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Next charge" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Details" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Download license" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Logged in!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Assign collection access" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index dc64a6a0d8c..bb52d98e1e4 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Sonraki ödeme" }, + "nextChargeHeader": { + "message": "Sonraki ödeme" + }, + "plan": { + "message": "Paket" + }, "details": { "message": "Ayrıntılar" }, + "discount": { + "message": "indirim" + }, "downloadLicense": { "message": "Lisansı indir" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Bitwarden uzantınızı açın" }, - "autoConfirmStep2a": { - "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Etkinleştir", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " seçeneğini seçin", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Giriş yapıldı!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "Koleksiyon erişimi ata" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Kasa zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Zaman aşımı ayarlarınıza kurumsal ilke gereksinimleri uygulandı" + }, + "vaultTimeoutTooLarge": { + "message": "Kasa zaman aşımınız, kuruluşunuz tarafından belirlenen kısıtlamaları aşıyor." + }, + "neverLockWarning": { + "message": "\"Asla\" seçeneğini kullanmak istediğinizden emin misiniz? Kilit seçeneklerinizi \"Asla\" olarak ayarlarsanız kasanızın şifreleme anahtarı cihazınızda saklanacaktır. Bu seçeneği kullanırsanız cihazınızı çok iyi korumalısınız." + }, + "sessionTimeoutSettingsAction": { + "message": "Zaman aşımı eylemi" + }, + "sessionTimeoutHeader": { + "message": "Oturum zaman aşımı" + }, + "appearance": { + "message": "Görünüm" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Zaman aşımınız kuruluşunuzun belirlediği maksimum süreyi aşıyor: Maksimum $HOURS$ saat $MINUTES$ dakika", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index e1a00b3dde4..6fa971b98a5 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Applications needing review" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ new applications", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Наступна оплата" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Подробиці" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Завантажити ліцензію" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Run report" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "How to turn on automatic user confirmation" }, - "autoConfirmStep1": { - "message": "Open your Bitwarden extension." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "Select", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Turn on.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Successfully opened the Bitwarden browser extension. You can now activate the automatic user confirmation setting." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Ви увійшли!" }, - "beta": { - "message": "Бета" - }, "assignCollectionAccess": { "message": "Призначити доступ до збірки" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Start free Families trial" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 08df68b6d33..6e6ba52daad 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "Ứng dụng cần xem lại" }, + "newApplicationsCardTitle": { + "message": "Review new applications" + }, "newApplicationsWithCount": { "message": "$COUNT$ ứng dụng mới", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "Hiện tại không có ứng dụng mới nào để đánh giá" }, + "organizationHasItemsSavedForApplications": { + "message": "Your organization has items saved for $COUNT$ applications", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "Review applications to secure the items most critical to your organization's security" + }, + "reviewApplications": { + "message": "Review applications" + }, "prioritizeCriticalApplications": { "message": "Ưu tiên các ứng dụng quan trọng" }, @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "Lần thanh toán tiếp theo" }, + "nextChargeHeader": { + "message": "Next Charge" + }, + "plan": { + "message": "Plan" + }, "details": { "message": "Chi tiết" }, + "discount": { + "message": "discount" + }, "downloadLicense": { "message": "Tải về tệp giấy phép" }, @@ -4442,6 +4469,30 @@ "generatingYourAccessIntelligence": { "message": "Generating your Access Intelligence..." }, + "fetchingMemberData": { + "message": "Fetching member data..." + }, + "analyzingPasswordHealth": { + "message": "Analyzing password health..." + }, + "calculatingRiskScores": { + "message": "Calculating risk scores..." + }, + "generatingReportData": { + "message": "Generating report data..." + }, + "savingReport": { + "message": "Saving report..." + }, + "compilingInsights": { + "message": "Compiling insights..." + }, + "loadingProgress": { + "message": "Loading progress" + }, + "thisMightTakeFewMinutes": { + "message": "This might take a few minutes." + }, "riskInsightsRunReport": { "message": "Chạy báo cáo" }, @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "Cách bật xác nhận người dùng tự động" }, - "autoConfirmStep1": { - "message": "Mở tiện ích mở rộng Bitwarden của bạn." + "autoConfirmExtension1": { + "message": "Open your Bitwarden extension" }, - "autoConfirmStep2a": { - "message": "Chọn", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension2": { + "message": "Select", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " Bật.", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " Turn on", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "Đã mở tiện ích mở rộng Bitwarden trong trình duyệt. Giờ bạn có thể bật tùy chọn xác nhận người dùng tự động." @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "Đã đăng nhập!" }, - "beta": { - "message": "Phiên bản Beta" - }, "assignCollectionAccess": { "message": "Gán quyền truy cập bộ sưu tập" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "Bắt đầu dùng thử Gói Gia đình miễn phí" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "Set up an unlock method to change your vault timeout action." + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "Enterprise policy requirements have been applied to your timeout options" + }, + "vaultTimeoutTooLarge": { + "message": "Your vault timeout exceeds the restrictions set by your organization." + }, + "neverLockWarning": { + "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + }, + "sessionTimeoutSettingsAction": { + "message": "Timeout action" + }, + "sessionTimeoutHeader": { + "message": "Session timeout" + }, + "appearance": { + "message": "Appearance" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index c8d46b34254..cce4544ac7b 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -188,7 +188,7 @@ } }, "noApplicationsInOrgDescription": { - "message": "导入您组织的登录数据,以开始监控凭据安全风险。导入后您将能够:" + "message": "导入您组织的登录数据,以开始监测凭据安全风险。导入后您将能够:" }, "benefit1Title": { "message": "优先处理风险" @@ -203,7 +203,7 @@ "message": "为存在风险的成员分配引导式任务,以轮换存在风险的凭据" }, "benefit3Title": { - "message": "监控进度" + "message": "监测进度" }, "benefit3Description": { "message": "追踪变化趋势,展示安全改进成效" @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "应用程序需要审查" }, + "newApplicationsCardTitle": { + "message": "审查新应用程序" + }, "newApplicationsWithCount": { "message": "$COUNT$ 个新应用程序", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "目前没有新应用程序需要审查" }, + "organizationHasItemsSavedForApplications": { + "message": "您的组织已为 $COUNT$ 个应用程序保存了项目", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "审查应用程序以保护对您的组织安全最关键的项目" + }, + "reviewApplications": { + "message": "审查应用程序" + }, "prioritizeCriticalApplications": { "message": "优先处理关键应用程序" }, @@ -380,7 +398,7 @@ "message": "审查新应用程序" }, "reviewNewApplicationsDescription": { - "message": "我们突出显示了管理控制台中存储的新应用程序中存在风险的项目,这些项目使用了弱、暴露或重复使用的密码。" + "message": "我们突出显示了存储在管理控制台中的新应用程序的风险项目,这些项目使用了弱、暴露或重复使用的密码。" }, "clickIconToMarkAppAsCritical": { "message": "点击星形图标以将 App 标记为关键" @@ -3230,11 +3248,20 @@ "message": "状态" }, "nextCharge": { - "message": "下一次扣款" + "message": "下一次收费" + }, + "nextChargeHeader": { + "message": "下一次收费" + }, + "plan": { + "message": "方案" }, "details": { "message": "详细信息" }, + "discount": { + "message": "折扣" + }, "downloadLicense": { "message": "下载许可证" }, @@ -3808,7 +3835,7 @@ "message": "加载更多" }, "mobile": { - "message": "手机版应用", + "message": "移动端", "description": "Mobile app" }, "extension": { @@ -4440,7 +4467,31 @@ "message": "更新浏览器" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "正在生成 Access Intelligence..." + }, + "fetchingMemberData": { + "message": "正在获取成员数据..." + }, + "analyzingPasswordHealth": { + "message": "正在分析密码健康度..." + }, + "calculatingRiskScores": { + "message": "正在计算风险评分..." + }, + "generatingReportData": { + "message": "正在生成报告数据..." + }, + "savingReport": { + "message": "正在保存报告..." + }, + "compilingInsights": { + "message": "正在编译洞察..." + }, + "loadingProgress": { + "message": "加载进度" + }, + "thisMightTakeFewMinutes": { + "message": "这可能需要几分钟时间。" }, "riskInsightsRunReport": { "message": "运行报告" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "如何启用自动用户确认" }, - "autoConfirmStep1": { - "message": "打开您的 Bitwarden 扩展。" + "autoConfirmExtension1": { + "message": "打开您的 Bitwarden 扩展" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "选择", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": "启用。", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": "启用", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "成功打开 Bitwarden 浏览器扩展。您现在可以激活自动用户确认设置。" @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "已登录!" }, - "beta": { - "message": "Beta" - }, "assignCollectionAccess": { "message": "分配集合访问权限" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "开始免费家庭版试用" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "设置一个解锁方式以更改您的密码库超时动作。" + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "企业策略要求已应用到您的超时选项中" + }, + "vaultTimeoutTooLarge": { + "message": "您的密码库超时超出了您组织设置的限制。" + }, + "neverLockWarning": { + "message": "确定要使用「从不」选项吗?将锁定选项设置为「从不」会将密码库的加密密钥存储在您的设备上。如果使用此选项,您必须确保您的设备安全。" + }, + "sessionTimeoutSettingsAction": { + "message": "超时动作" + }, + "sessionTimeoutHeader": { + "message": "会话超时" + }, + "appearance": { + "message": "外观" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "超时超出了您组织设置的限制:最多 $HOURS$ 小时 $MINUTES$ 分钟", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 4b6f0725cea..784027a735a 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -21,7 +21,7 @@ "message": "密碼風險" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "你沒有權限編輯這個項目" }, "reviewAtRiskPasswords": { "message": "檢視全部應用中具有風險的密碼 (弱、被暴露或重複使用)。選擇最重要的應用程式並優先採取安全措施,幫助使用者解決具有風險的密碼。" @@ -349,6 +349,9 @@ "applicationsNeedingReview": { "message": "需要檢視的應用程式" }, + "newApplicationsCardTitle": { + "message": "審查新應用程式" + }, "newApplicationsWithCount": { "message": "$COUNT$ 個新應用程式", "placeholders": { @@ -370,6 +373,21 @@ "noNewApplicationsToReviewAtThisTime": { "message": "目前沒有新的應用程式可供審查" }, + "organizationHasItemsSavedForApplications": { + "message": "您的組織已為 $COUNT$ 個應用程式儲存項目", + "placeholders": { + "count": { + "content": "$1", + "example": "310" + } + } + }, + "reviewApplicationsToSecureItems": { + "message": "審查應用程式以保護對組織安全最重要的項目" + }, + "reviewApplications": { + "message": "審核認領" + }, "prioritizeCriticalApplications": { "message": "優先處理關鍵應用程式" }, @@ -377,10 +395,10 @@ "message": "選擇對組織最關鍵的應用程式,然後將安全任務指派給成員以供解決。" }, "reviewNewApplications": { - "message": "Review new applications" + "message": "審查新應用程式" }, "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "message": "我們已在管理主控台中標示出新應用程式中密碼薄弱、已外洩或重複使用的高風險項目。" }, "clickIconToMarkAppAsCritical": { "message": "點擊星形圖示以將應用程式標記為關鍵" @@ -3232,9 +3250,18 @@ "nextCharge": { "message": "下一次扣款" }, + "nextChargeHeader": { + "message": "下一次收費" + }, + "plan": { + "message": "方案" + }, "details": { "message": "詳細資料" }, + "discount": { + "message": "折扣" + }, "downloadLicense": { "message": "下載授權證" }, @@ -4440,7 +4467,31 @@ "message": "更新瀏覽器" }, "generatingYourAccessIntelligence": { - "message": "Generating your Access Intelligence..." + "message": "正在產生您的存取智慧分析…" + }, + "fetchingMemberData": { + "message": "正在擷取成員資料…" + }, + "analyzingPasswordHealth": { + "message": "正在分析密碼安全狀況…" + }, + "calculatingRiskScores": { + "message": "正在計算風險分數…" + }, + "generatingReportData": { + "message": "正在產生報告資料..." + }, + "savingReport": { + "message": "正在儲存報告..." + }, + "compilingInsights": { + "message": "正在整理洞察結果…" + }, + "loadingProgress": { + "message": "載入進度中" + }, + "thisMightTakeFewMinutes": { + "message": "這可能需要幾分鐘。" }, "riskInsightsRunReport": { "message": "執行報告" @@ -5804,16 +5855,16 @@ "howToTurnOnAutoConfirm": { "message": "如何開啟自動使用者確認" }, - "autoConfirmStep1": { - "message": "開啟你的 Bitwarden 瀏覽器擴充套件。" + "autoConfirmExtension1": { + "message": "開啟你的 Bitwarden 瀏覽器擴充套件" }, - "autoConfirmStep2a": { + "autoConfirmExtension2": { "message": "選擇", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, - "autoConfirmStep2b": { - "message": " 開啟。", - "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on.'" + "autoConfirmExtension3": { + "message": " 開啟", + "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { "message": "已成功開啟 Bitwarden 瀏覽器擴充套件。您現在可以啟用自動使用者確認設定。" @@ -9525,9 +9576,6 @@ "loggedInExclamation": { "message": "已登入!" }, - "beta": { - "message": "Beta 版" - }, "assignCollectionAccess": { "message": "指派分類存取權限" }, @@ -12075,5 +12123,45 @@ }, "startFreeFamiliesTrial": { "message": "開始免費家庭試用" + }, + "unlockMethodNeededToChangeTimeoutActionDesc": { + "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" + }, + "vaultTimeoutPolicyAffectingOptions": { + "message": "企業政策已套用至您的逾時選項中" + }, + "vaultTimeoutTooLarge": { + "message": "您的密碼庫逾時時間超過組織設定的限制。" + }, + "neverLockWarning": { + "message": "您確定要使用「永不」選項嗎?將鎖定選項設定為「永不」會將密碼庫的加密金鑰儲存在您的裝置上。如果使用此選項,應確保您的裝置是安全的。" + }, + "sessionTimeoutSettingsAction": { + "message": "逾時後動作" + }, + "sessionTimeoutHeader": { + "message": "工作階段逾時" + }, + "appearance": { + "message": "外觀" + }, + "vaultTimeoutPolicyMaximumError": { + "message": "逾時時間超出了您組織設定的此限制:最多 $HOURS$ 小時 $MINUTES$ 分鐘", + "placeholders": { + "hours": { + "content": "$1", + "example": "5" + }, + "minutes": { + "content": "$2", + "example": "5" + } + } + }, + "confirmNoSelectedCriticalApplicationsTitle": { + "message": "No critical applications are selected" + }, + "confirmNoSelectedCriticalApplicationsDesc": { + "message": "Are you sure you want to continue?" } } From 0925f4fa788e1ac52078c6727772f6a13c49cfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= <dani-garcia@users.noreply.github.com> Date: Fri, 14 Nov 2025 15:22:31 +0100 Subject: [PATCH 135/249] Bundle windows crates in renovate config (#17365) --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index d2f0c75b9f5..6b34998b99b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -231,6 +231,7 @@ "webpack-node-externals", "widestring", "windows", + "windows-core", "windows-future", "windows-registry", "zbus", @@ -255,6 +256,11 @@ groupName: "zbus", matchPackageNames: ["zbus", "zbus_polkit"], }, + { + // We need to group all windows-related packages together to avoid build errors caused by version incompatibilities. + groupName: "windows", + matchPackageNames: ["windows", "windows-core", "windows-future", "windows-registry"], + }, { // We group all webpack build-related minor and patch updates together to reduce PR noise. // We include patch updates here because we want PRs for webpack patch updates and it's in this group. From 099a4a0f0312c9d75caa7664a885b288d4954ad0 Mon Sep 17 00:00:00 2001 From: Brandon Treston <btreston@bitwarden.com> Date: Fri, 14 Nov 2025 11:43:10 -0500 Subject: [PATCH 136/249] [PM-28216] Add org ability check for one time dialog (#17372) * add org ability check for one time dialog * exclude providers (cautionary step) and add tests --- .../vault/individual-vault/vault.component.ts | 2 +- .../models/domain/organization.spec.ts | 115 ++++++++++++++++++ .../models/domain/organization.ts | 8 ++ 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 07e810a0cbf..3b0a7a6f141 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -1623,7 +1623,7 @@ export class VaultComponent<C extends CipherViewLike> implements OnInit, OnDestr !policyEnabled && autoConfirmState.showSetupDialog && !!organization && - (organization.canManageUsers || organization.canManagePolicies); + organization.canEnableAutoConfirmPolicy; if (showDialog) { await this.openAutoConfirmFeatureDialog(organization); diff --git a/libs/common/src/admin-console/models/domain/organization.spec.ts b/libs/common/src/admin-console/models/domain/organization.spec.ts index 2ce674dcb36..5765e84dfb2 100644 --- a/libs/common/src/admin-console/models/domain/organization.spec.ts +++ b/libs/common/src/admin-console/models/domain/organization.spec.ts @@ -32,6 +32,7 @@ describe("Organization", () => { useSecretsManager: true, usePasswordManager: true, useActivateAutofillPolicy: false, + useAutomaticUserConfirmation: false, selfHost: false, usersGetPremium: false, seats: 10, @@ -179,4 +180,118 @@ describe("Organization", () => { expect(organization.canManageDeviceApprovals).toBe(true); }); }); + + describe("canEnableAutoConfirmPolicy", () => { + it("should return false when user cannot manage users or policies", () => { + data.type = OrganizationUserType.User; + data.permissions.manageUsers = false; + data.permissions.managePolicies = false; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + + it("should return false when user can manage users but useAutomaticUserConfirmation is false", () => { + data.type = OrganizationUserType.Admin; + data.useAutomaticUserConfirmation = false; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + + it("should return false when user has manageUsers permission but useAutomaticUserConfirmation is false", () => { + data.type = OrganizationUserType.User; + data.permissions.manageUsers = true; + data.useAutomaticUserConfirmation = false; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + + it("should return false when user can manage policies but useAutomaticUserConfirmation is false", () => { + data.type = OrganizationUserType.Admin; + data.usePolicies = true; + data.useAutomaticUserConfirmation = false; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + + it("should return false when user has managePolicies permission but usePolicies is false", () => { + data.type = OrganizationUserType.User; + data.permissions.managePolicies = true; + data.usePolicies = false; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + + it("should return true when admin has useAutomaticUserConfirmation enabled", () => { + data.type = OrganizationUserType.Admin; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(true); + }); + + it("should return true when owner has useAutomaticUserConfirmation enabled", () => { + data.type = OrganizationUserType.Owner; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(true); + }); + + it("should return true when user has manageUsers permission and useAutomaticUserConfirmation is enabled", () => { + data.type = OrganizationUserType.User; + data.permissions.manageUsers = true; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(true); + }); + + it("should return true when user has managePolicies permission, usePolicies is true, and useAutomaticUserConfirmation is enabled", () => { + data.type = OrganizationUserType.User; + data.permissions.managePolicies = true; + data.usePolicies = true; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(true); + }); + + it("should return true when user has both manageUsers and managePolicies permissions with useAutomaticUserConfirmation enabled", () => { + data.type = OrganizationUserType.User; + data.permissions.manageUsers = true; + data.permissions.managePolicies = true; + data.usePolicies = true; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(true); + }); + + it("should return false when provider user has useAutomaticUserConfirmation enabled", () => { + data.type = OrganizationUserType.Owner; + data.isProviderUser = true; + data.useAutomaticUserConfirmation = true; + + const organization = new Organization(data); + + expect(organization.canEnableAutoConfirmPolicy).toBe(false); + }); + }); }); diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 55682e62357..458ae1e8f0c 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -310,6 +310,14 @@ export class Organization { return this.isAdmin || this.permissions.manageResetPassword; } + get canEnableAutoConfirmPolicy() { + return ( + (this.canManageUsers || this.canManagePolicies) && + this.useAutomaticUserConfirmation && + !this.isProviderUser + ); + } + get canManageDeviceApprovals() { return ( (this.isAdmin || this.permissions.manageResetPassword) && From 3b97093338d83798fa3cc17f8161aa9e6f2a02b4 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Fri, 14 Nov 2025 11:54:08 -0500 Subject: [PATCH 137/249] fix(desktop): persist zoom state across vault locks (#17217) * fix(desktop): persist zoom state across vault locks Replace role-based zoom menu items with custom click handlers to fix zoom persistence issue where keyboard shortcuts (Ctrl+/-/0, Cmd+/-/0) weren't saving zoom changes after vault lock. Changes: - Add custom click handlers for zoomIn/zoomOut/resetZoom menu items - Add WindowMain.saveZoomFactor() method for immediate persistence - Pass WindowMain dependency to ViewMenu constructor - Update zoom-changed event comment to clarify coverage - Maintain existing mouse wheel zoom persistence via zoom-changed event Fixes: PM-791 Fixes: https://github.com/bitwarden/clients/issues/4675 * chore: update to macos-15 runners * review: downgrade macos build runner to 14 * review: align step with min zoom level * cleanup from merge --- apps/desktop/src/main/menu/menu.view.ts | 30 +++++++++++++++++++++---- apps/desktop/src/main/menu/menubar.ts | 2 +- apps/desktop/src/main/window.main.ts | 9 +++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/main/menu/menu.view.ts b/apps/desktop/src/main/menu/menu.view.ts index 962c57fdb60..d24128730cc 100644 --- a/apps/desktop/src/main/menu/menu.view.ts +++ b/apps/desktop/src/main/menu/menu.view.ts @@ -6,6 +6,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { isDev } from "../../utils"; +import { WindowMain } from "../window.main"; import { IMenubarMenu } from "./menubar"; @@ -42,11 +43,18 @@ export class ViewMenu implements IMenubarMenu { private readonly _i18nService: I18nService; private readonly _messagingService: MessagingService; private readonly _isLocked: boolean; + private readonly _windowMain: WindowMain; - constructor(i18nService: I18nService, messagingService: MessagingService, isLocked: boolean) { + constructor( + i18nService: I18nService, + messagingService: MessagingService, + isLocked: boolean, + windowMain: WindowMain, + ) { this._i18nService = i18nService; this._messagingService = messagingService; this._isLocked = isLocked; + this._windowMain = windowMain; } private get searchVault(): MenuItemConstructorOptions { @@ -86,7 +94,12 @@ export class ViewMenu implements IMenubarMenu { return { id: "zoomIn", label: this.localize("zoomIn"), - role: "zoomIn", + click: async () => { + const currentZoom = this._windowMain.win.webContents.zoomFactor; + const newZoom = currentZoom + 0.1; + this._windowMain.win.webContents.zoomFactor = newZoom; + await this._windowMain.saveZoomFactor(newZoom); + }, accelerator: "CmdOrCtrl+=", }; } @@ -95,7 +108,12 @@ export class ViewMenu implements IMenubarMenu { return { id: "zoomOut", label: this.localize("zoomOut"), - role: "zoomOut", + click: async () => { + const currentZoom = this._windowMain.win.webContents.zoomFactor; + const newZoom = Math.max(0.2, currentZoom - 0.1); + this._windowMain.win.webContents.zoomFactor = newZoom; + await this._windowMain.saveZoomFactor(newZoom); + }, accelerator: "CmdOrCtrl+-", }; } @@ -104,7 +122,11 @@ export class ViewMenu implements IMenubarMenu { return { id: "resetZoom", label: this.localize("resetZoom"), - role: "resetZoom", + click: async () => { + const newZoom = 1.0; + this._windowMain.win.webContents.zoomFactor = newZoom; + await this._windowMain.saveZoomFactor(newZoom); + }, accelerator: "CmdOrCtrl+0", }; } diff --git a/apps/desktop/src/main/menu/menubar.ts b/apps/desktop/src/main/menu/menubar.ts index 8ac3a084d95..0a00a67b84a 100644 --- a/apps/desktop/src/main/menu/menubar.ts +++ b/apps/desktop/src/main/menu/menubar.ts @@ -86,7 +86,7 @@ export class Menubar { updateRequest?.restrictedCipherTypes, ), new EditMenu(i18nService, messagingService, isLocked), - new ViewMenu(i18nService, messagingService, isLocked), + new ViewMenu(i18nService, messagingService, isLocked, windowMain), new AccountMenu( i18nService, messagingService, diff --git a/apps/desktop/src/main/window.main.ts b/apps/desktop/src/main/window.main.ts index f8ea7551c47..d148a1a35f8 100644 --- a/apps/desktop/src/main/window.main.ts +++ b/apps/desktop/src/main/window.main.ts @@ -303,7 +303,9 @@ export class WindowMain { this.win.webContents.zoomFactor = this.windowStates[mainWindowSizeKey].zoomFactor ?? 1.0; }); - // Persist zoom changes immediately when user zooms in/out or resets zoom + // Persist zoom changes from mouse wheel and programmatic zoom operations + // NOTE: This event does NOT fire for keyboard shortcuts (Ctrl+/-/0, Cmd+/-/0) + // which are handled by custom menu click handlers in ViewMenu // We can't depend on higher level web events (like close) to do this // because locking the vault resets window state. this.win.webContents.on("zoom-changed", async () => { @@ -432,6 +434,11 @@ export class WindowMain { await this.desktopSettingsService.setAlwaysOnTop(this.enableAlwaysOnTop); } + async saveZoomFactor(zoomFactor: number) { + this.windowStates[mainWindowSizeKey].zoomFactor = zoomFactor; + await this.desktopSettingsService.setWindow(this.windowStates[mainWindowSizeKey]); + } + private windowStateChangeHandler(configKey: string, win: BrowserWindow) { global.clearTimeout(this.windowStateChangeTimer); this.windowStateChangeTimer = global.setTimeout(async () => { From fdb2f8b55375e2ba2682e09d71db457e5eec4c93 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Fri, 14 Nov 2025 12:44:32 -0500 Subject: [PATCH 138/249] [PM-4903] - If you back out of autofill flow from locked vault screen, credentials autofilled on normal unlock (#17283) * PM-4903- added a check for auth status and popout tabs, if no popup tab and auth is locked, abandon autofill * add test * clear all notifications if unlock popout closed * add more tests and use tabid for performance optimization --- .../notification.background.spec.ts | 58 +++++++++++++++++++ .../background/notification.background.ts | 44 +++++++++++++- .../src/background/runtime.background.ts | 3 + 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index f9e2e1c534f..8df21bc66ef 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1530,5 +1530,63 @@ describe("NotificationBackground", () => { expect(environmentServiceSpy).toHaveBeenCalled(); }); }); + + describe("handleUnlockPopoutClosed", () => { + let onRemovedListeners: Array<(tabId: number, removeInfo: chrome.tabs.OnRemovedInfo) => void>; + let tabsQuerySpy: jest.SpyInstance; + + beforeEach(() => { + onRemovedListeners = []; + chrome.tabs.onRemoved.addListener = jest.fn((listener) => { + onRemovedListeners.push(listener); + }); + chrome.runtime.getURL = jest.fn().mockReturnValue("chrome-extension://id/popup/index.html"); + notificationBackground.init(); + }); + + const triggerTabRemoved = async (tabId: number) => { + onRemovedListeners[0](tabId, mock<chrome.tabs.OnRemovedInfo>()); + await flushPromises(); + }; + + it("sends abandon message when unlock popout is closed and vault is locked", async () => { + activeAccountStatusMock$.next(AuthenticationStatus.Locked); + tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([]); + + await triggerTabRemoved(1); + + expect(tabsQuerySpy).toHaveBeenCalled(); + expect(messagingService.send).toHaveBeenCalledWith("abandonAutofillPendingNotifications"); + }); + + it("uses tracked tabId for fast lookup when available", async () => { + activeAccountStatusMock$.next(AuthenticationStatus.Locked); + tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([ + { + id: 123, + url: "chrome-extension://id/popup/index.html?singleActionPopout=auth_unlockExtension", + } as chrome.tabs.Tab, + ]); + + await triggerTabRemoved(999); + tabsQuerySpy.mockClear(); + messagingService.send.mockClear(); + + await triggerTabRemoved(123); + + expect(tabsQuerySpy).not.toHaveBeenCalled(); + expect(messagingService.send).toHaveBeenCalledWith("abandonAutofillPendingNotifications"); + }); + + it("returns early when vault is unlocked", async () => { + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([]); + + await triggerTabRemoved(1); + + expect(tabsQuerySpy).not.toHaveBeenCalled(); + expect(messagingService.send).not.toHaveBeenCalled(); + }); + }); }); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index e27b50f13cd..de1514f0342 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -45,7 +45,7 @@ import { SecurityTask } from "@bitwarden/common/vault/tasks/models/security-task // FIXME (PM-22628): Popup imports are forbidden in background // eslint-disable-next-line no-restricted-imports -import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window"; +import { AuthPopoutType, openUnlockPopout } from "../../auth/popup/utils/auth-popout-window"; import { BrowserApi } from "../../platform/browser/browser-api"; // FIXME (PM-22628): Popup imports are forbidden in background // eslint-disable-next-line no-restricted-imports @@ -89,6 +89,7 @@ export default class NotificationBackground { ExtensionCommand.AutofillCard, ExtensionCommand.AutofillIdentity, ]); + private unlockPopoutTabId?: number; private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = { bgAdjustNotificationBar: ({ message, sender }) => this.handleAdjustNotificationBarMessage(message, sender), @@ -146,6 +147,7 @@ export default class NotificationBackground { } this.setupExtensionMessageListener(); + this.setupUnlockPopoutCloseListener(); this.cleanupNotificationQueue(); } @@ -1163,6 +1165,7 @@ export default class NotificationBackground { message: NotificationBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ): Promise<void> { + this.unlockPopoutTabId = undefined; const messageData = message.data as LockedVaultPendingNotificationsData; const retryCommand = messageData.commandToRetry.message.command as ExtensionCommandType; if (this.allowedRetryCommands.has(retryCommand)) { @@ -1313,4 +1316,43 @@ export default class NotificationBackground { const tabDomain = Utils.getDomain(tab.url); return tabDomain === queueMessage.domain || tabDomain === Utils.getDomain(queueMessage.tab.url); } + + private setupUnlockPopoutCloseListener() { + chrome.tabs.onRemoved.addListener(async (tabId: number) => { + await this.handleUnlockPopoutClosed(tabId); + }); + } + + /** + * If the unlock popout is closed while the vault + * is still locked and there are pending autofill notifications, abandon them. + */ + private async handleUnlockPopoutClosed(removedTabId: number) { + const authStatus = await this.getAuthStatus(); + if (authStatus >= AuthenticationStatus.Unlocked) { + this.unlockPopoutTabId = undefined; + return; + } + + if (this.unlockPopoutTabId === removedTabId) { + this.unlockPopoutTabId = undefined; + this.messagingService.send("abandonAutofillPendingNotifications"); + return; + } + + if (this.unlockPopoutTabId) { + return; + } + + const extensionUrl = chrome.runtime.getURL("popup/index.html"); + const unlockPopoutTabs = (await BrowserApi.tabsQuery({ url: `${extensionUrl}*` })).filter( + (tab) => tab.url?.includes(`singleActionPopout=${AuthPopoutType.unlockExtension}`), + ); + + if (unlockPopoutTabs.length === 0) { + this.messagingService.send("abandonAutofillPendingNotifications"); + } else if (unlockPopoutTabs[0].id) { + this.unlockPopoutTabId = unlockPopoutTabs[0].id; + } + } } diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index de0d79a89db..798a7583f85 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -256,6 +256,9 @@ export default class RuntimeBackground { case "addToLockedVaultPendingNotifications": this.lockedVaultPendingNotifications.push(msg.data); break; + case "abandonAutofillPendingNotifications": + this.lockedVaultPendingNotifications = []; + break; case "lockVault": await this.lockService.lock(msg.userId); break; From b56229dd2880448ef212ec891a79d4645639d251 Mon Sep 17 00:00:00 2001 From: Mike Amirault <mamirault@bitwarden.com> Date: Fri, 14 Nov 2025 14:27:40 -0500 Subject: [PATCH 139/249] Remove import page banner when under org policy (#17348) --- libs/importer/src/components/import.component.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/importer/src/components/import.component.html b/libs/importer/src/components/import.component.html index bd4afaf364b..dfee02acf5a 100644 --- a/libs/importer/src/components/import.component.html +++ b/libs/importer/src/components/import.component.html @@ -1,6 +1,3 @@ -<bit-callout type="info" *ngIf="importBlockedByPolicy"> - {{ "personalOwnershipPolicyInEffectImports" | i18n }} -</bit-callout> <bit-callout [title]="'restrictCardTypeImport' | i18n" type="info" From 8a3f1ee1a446cd61ff52b2bfa19334e97ab9c6f6 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Fri, 14 Nov 2025 16:16:08 -0500 Subject: [PATCH 140/249] [PM-26687] send skeleton (#17333) * adding skeleton to send --- .../popup/send-v2/send-v2.component.html | 11 +++++-- .../tools/popup/send-v2/send-v2.component.ts | 30 +++++++++++++++++-- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index 997b65e9934..0bcbd47a145 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -1,4 +1,4 @@ -<popup-page [loading]="sendsLoading$ | async"> +<popup-page [loading]="showSpinnerLoaders$ | async" [hideOverflow]="showSkeletonsLoaders$ | async"> <popup-header slot="header" [pageTitle]="'send' | i18n"> <ng-container slot="end"> <tools-new-send-dropdown *ngIf="!sendsDisabled"></tools-new-send-dropdown> @@ -6,7 +6,7 @@ <app-current-account></app-current-account> </ng-container> </popup-header> - <ng-container slot="above-scroll-area" *ngIf="!(sendsLoading$ | async)"> + <ng-container slot="above-scroll-area"> <bit-callout *ngIf="sendsDisabled" [title]="'sendDisabled' | i18n"> {{ "sendDisabledWarning" | i18n }} </bit-callout> @@ -34,7 +34,7 @@ </bit-no-items> </div> - <ng-container *ngIf="listState !== sendState.Empty"> + <ng-container *ngIf="listState !== sendState.Empty && !(showSkeletonsLoaders$ | async)"> <div *ngIf="listState === sendState.NoResults" class="tw-flex tw-flex-col tw-justify-center tw-h-auto tw-pt-12" @@ -46,4 +46,9 @@ </div> <app-send-list-items-container [headerText]="title | i18n" [sends]="sends$ | async" /> </ng-container> + @if (showSkeletonsLoaders$ | async) { + <vault-fade-in-skeleton> + <vault-loading-skeleton></vault-loading-skeleton> + </vault-fade-in-skeleton> + } </popup-page> diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 1272a86be17..43a1119deca 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -1,15 +1,18 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { combineLatest, switchMap } from "rxjs"; +import { combineLatest, distinctUntilChanged, map, shareReplay, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NoResults, NoSendsIcon } from "@bitwarden/assets/svg"; +import { VaultLoadingSkeletonComponent } from "@bitwarden/browser/vault/popup/components/vault-loading-skeleton/vault-loading-skeleton.component"; import { BrowserPremiumUpgradePromptService } from "@bitwarden/browser/vault/popup/services/browser-premium-upgrade-prompt.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { @@ -31,6 +34,7 @@ import { CurrentAccountComponent } from "../../../auth/popup/account-switching/c import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { VaultFadeInOutSkeletonComponent } from "../../../vault/popup/components/vault-fade-in-out-skeleton/vault-fade-in-out-skeleton.component"; // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums @@ -64,6 +68,8 @@ export enum SendState { SendListFiltersComponent, SendSearchComponent, TypographyModule, + VaultFadeInOutSkeletonComponent, + VaultLoadingSkeletonComponent, ], }) export class SendV2Component implements OnDestroy { @@ -72,7 +78,26 @@ export class SendV2Component implements OnDestroy { protected listState: SendState | null = null; protected sends$ = this.sendItemsService.filteredAndSortedSends$; - protected sendsLoading$ = this.sendItemsService.loading$; + private skeletonFeatureFlag$ = this.configService.getFeatureFlag$( + FeatureFlag.VaultLoadingSkeletons, + ); + protected sendsLoading$ = this.sendItemsService.loading$.pipe( + distinctUntilChanged(), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + /** Spinner Loading State */ + protected showSpinnerLoaders$ = combineLatest([ + this.sendsLoading$, + this.skeletonFeatureFlag$, + ]).pipe(map(([loading, skeletonsEnabled]) => loading && !skeletonsEnabled)); + + /** Skeleton Loading State */ + protected showSkeletonsLoaders$ = combineLatest([ + this.sendsLoading$, + this.skeletonFeatureFlag$, + ]).pipe(map(([loading, skeletonsEnabled]) => loading && skeletonsEnabled)); + protected title: string = "allSends"; protected noItemIcon = NoSendsIcon; protected noResultsIcon = NoResults; @@ -84,6 +109,7 @@ export class SendV2Component implements OnDestroy { protected sendListFiltersService: SendListFiltersService, private policyService: PolicyService, private accountService: AccountService, + private configService: ConfigService, ) { combineLatest([ this.sendItemsService.emptyList$, From 9cd73b8738a09acb52505e1d1befe8a2f6174336 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:28:05 -0500 Subject: [PATCH 141/249] Auth/PM-22661 - SendTokenService - improve expired token scenario docs on abstraction (#17371) * PM-22661 - SendTokenService - improve expired token scenario docs on abstraction * PM-22661 - SendTokenService - further clarification --- .../src/auth/send-access/abstractions/send-token.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/auth/send-access/abstractions/send-token.service.ts b/libs/common/src/auth/send-access/abstractions/send-token.service.ts index 3ecdc101892..e423713a283 100644 --- a/libs/common/src/auth/send-access/abstractions/send-token.service.ts +++ b/libs/common/src/auth/send-access/abstractions/send-token.service.ts @@ -13,7 +13,7 @@ export abstract class SendTokenService { /** * Attempts to retrieve a {@link SendAccessToken} for the given sendId. * If the access token is found in session storage and is not expired, then it returns the token. - * If the access token is expired, then it returns a {@link TryGetSendAccessTokenError} expired error. + * If the access token found in session storage is expired, then it returns a {@link TryGetSendAccessTokenError} expired error and clears the token from storage so that a subsequent call can attempt to retrieve a new token. * If an access token is not found in storage, then it attempts to retrieve it from the server (will succeed for sends that don't require any credentials to view). * If the access token is successfully retrieved from the server, then it stores the token in session storage and returns it. * If an access token cannot be granted b/c the send requires credentials, then it returns a {@link TryGetSendAccessTokenError} indicating which credentials are required. From a4d773537e3c6c9f7f96c29ce593ead0a6846932 Mon Sep 17 00:00:00 2001 From: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> Date: Fri, 14 Nov 2025 16:07:10 -0800 Subject: [PATCH 142/249] [PM-27465] Fixing cancel button on Send and Vault export (#17138) --- .../tools/popup/send-v2/add-edit/send-add-edit.component.html | 2 +- .../popup/settings/export/export-browser-v2.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html index c6ea52aff62..a72847a5bf2 100644 --- a/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html +++ b/apps/browser/src/tools/popup/send-v2/add-edit/send-add-edit.component.html @@ -16,7 +16,7 @@ <button bitButton type="submit" form="sendForm" buttonType="primary" #submitBtn> {{ "save" | i18n }} </button> - <button bitButton type="button" buttonType="secondary" popupBackAction> + <button bitButton type="button" buttonType="secondary" [popupBackAction]> {{ "cancel" | i18n }} </button> <button diff --git a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html index 8493fa5fee7..d6bf3a3a253 100644 --- a/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html +++ b/apps/browser/src/tools/popup/settings/export/export-browser-v2.component.html @@ -23,7 +23,7 @@ > {{ "exportVault" | i18n }} </button> - <button bitButton type="button" buttonType="secondary" popupBackAction> + <button bitButton type="button" buttonType="secondary" [popupBackAction]> {{ "cancel" | i18n }} </button> </popup-footer> From c67715ea29e12ce255b849ba4492afce3eda67af Mon Sep 17 00:00:00 2001 From: Matt Gibson <mgibson@bitwarden.com> Date: Mon, 17 Nov 2025 07:37:36 -0800 Subject: [PATCH 143/249] [PM-28038][PM-28276] Ignore url case for origin matching (#17355) * ignore url case for origin matching * Fixup typo * Inject log services --- apps/browser/src/background/main.background.ts | 2 +- apps/browser/src/platform/browser/browser-api.ts | 4 ++-- .../platform/storage/background-memory-storage.service.ts | 5 +++-- .../storage/memory-storage-service-interactions.spec.ts | 7 ++++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index cff783942fe..f59b6648486 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -548,7 +548,7 @@ export default class MainBackground { this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session this.memoryStorageService = this.memoryStorageForStateProviders; } else { - this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory + this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(this.logService); // mv2 stores to memory this.memoryStorageService = this.memoryStorageForStateProviders; } diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 76ec18f496f..723df95ef63 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -60,8 +60,8 @@ export class BrowserApi { } // Normalize both URLs by removing trailing slashes - const normalizedOrigin = sender.origin.replace(/\/$/, ""); - const normalizedExtensionUrl = extensionUrl.replace(/\/$/, ""); + const normalizedOrigin = sender.origin.replace(/\/$/, "").toLowerCase(); + const normalizedExtensionUrl = extensionUrl.replace(/\/$/, "").toLowerCase(); if (!normalizedOrigin.startsWith(normalizedExtensionUrl)) { logger?.warning( diff --git a/apps/browser/src/platform/storage/background-memory-storage.service.ts b/apps/browser/src/platform/storage/background-memory-storage.service.ts index 5e1bff99c39..e4431c30db6 100644 --- a/apps/browser/src/platform/storage/background-memory-storage.service.ts +++ b/apps/browser/src/platform/storage/background-memory-storage.service.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { LogService } from "@bitwarden/logging"; import { SerializedMemoryStorageService } from "@bitwarden/storage-core"; import { BrowserApi } from "../browser/browser-api"; @@ -11,14 +12,14 @@ import { portName } from "./port-name"; export class BackgroundMemoryStorageService extends SerializedMemoryStorageService { private _ports: chrome.runtime.Port[] = []; - constructor() { + constructor(private readonly logService: LogService) { super(); BrowserApi.addListener(chrome.runtime.onConnect, (port) => { if (port.name !== portName(chrome.storage.session)) { return; } - if (!BrowserApi.senderIsInternal(port.sender)) { + if (!BrowserApi.senderIsInternal(port.sender, this.logService)) { return; } diff --git a/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts b/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts index 4a8f5d3f2ff..8004559f57c 100644 --- a/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts +++ b/apps/browser/src/platform/storage/memory-storage-service-interactions.spec.ts @@ -4,6 +4,9 @@ */ import { trackEmissions } from "@bitwarden/common/../spec/utils"; +import { mock, MockProxy } from "jest-mock-extended"; + +import { LogService } from "@bitwarden/logging"; import { mockPorts } from "../../../spec/mock-port.spec-util"; @@ -14,11 +17,13 @@ import { ForegroundMemoryStorageService } from "./foreground-memory-storage.serv describe.skip("foreground background memory storage interaction", () => { let foreground: ForegroundMemoryStorageService; let background: BackgroundMemoryStorageService; + let logService: MockProxy<LogService>; beforeEach(() => { mockPorts(); + logService = mock(); - background = new BackgroundMemoryStorageService(); + background = new BackgroundMemoryStorageService(logService); foreground = new ForegroundMemoryStorageService(); }); From a2abbd09bf40c06268ef38803b4e7148684607b3 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Mon, 17 Nov 2025 08:14:50 -0800 Subject: [PATCH 144/249] Desktop Native compile debug builds with debug log level (#17357) * Desktop Native compile debug builds with debug log level * typo in code comment --- .github/workflows/build-desktop.yml | 16 ++++++++-------- apps/desktop/desktop_native/napi/package.json | 2 +- .../desktop_native/napi/scripts/build.js | 14 ++++++++++++++ apps/desktop/desktop_native/napi/src/lib.rs | 17 +++++++++++------ 4 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 apps/desktop/desktop_native/napi/scripts/build.js diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index f651af9dd7d..98192ea9e08 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -251,7 +251,7 @@ jobs: TARGET: musl run: | rustup target add x86_64-unknown-linux-musl - node build.js --target=x86_64-unknown-linux-musl --release + node build.js --target=x86_64-unknown-linux-musl - name: Build application run: npm run dist:lin @@ -414,7 +414,7 @@ jobs: TARGET: musl run: | rustup target add aarch64-unknown-linux-musl - node build.js --target=aarch64-unknown-linux-musl --release + node build.js --target=aarch64-unknown-linux-musl - name: Check index.d.ts generated if: github.event_name == 'pull_request' && steps.cache.outputs.cache-hit != 'true' @@ -995,12 +995,12 @@ jobs: cache: 'npm' cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} - + - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.14' - + - name: Set up Node-gyp run: python3 -m pip install setuptools @@ -1232,12 +1232,12 @@ jobs: cache: 'npm' cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} - + - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.14' - + - name: Set up Node-gyp run: python3 -m pip install setuptools @@ -1504,12 +1504,12 @@ jobs: cache: 'npm' cache-dependency-path: '**/package-lock.json' node-version: ${{ env._NODE_VERSION }} - + - name: Set up Python uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: '3.14' - + - name: Set up Node-gyp run: python3 -m pip install setuptools diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index d557ccfd259..ca17377c9f2 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "napi build --platform --js false", + "build": "node scripts/build.js", "test": "cargo test" }, "author": "", diff --git a/apps/desktop/desktop_native/napi/scripts/build.js b/apps/desktop/desktop_native/napi/scripts/build.js new file mode 100644 index 00000000000..a6680f5d311 --- /dev/null +++ b/apps/desktop/desktop_native/napi/scripts/build.js @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const { execSync } = require('child_process'); + +const args = process.argv.slice(2); +const isRelease = args.includes('--release'); + +if (isRelease) { + console.log('Building release mode.'); +} else { + console.log('Building debug mode.'); + process.env.RUST_LOG = 'debug'; +} + +execSync(`napi build --platform --js false`, { stdio: 'inherit', env: process.env }); diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 39e57bd0bb5..01d60ff5f56 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -957,10 +957,7 @@ pub mod logging { use tracing::Level; use tracing_subscriber::fmt::format::{DefaultVisitor, Writer}; use tracing_subscriber::{ - filter::{EnvFilter, LevelFilter}, - layer::SubscriberExt, - util::SubscriberInitExt, - Layer, + filter::EnvFilter, layer::SubscriberExt, util::SubscriberInitExt, Layer, }; struct JsLogger(OnceLock<ThreadsafeFunction<(LogLevel, String), CalleeHandled>>); @@ -1044,9 +1041,17 @@ pub mod logging { pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) { let _ = JS_LOGGER.0.set(js_log_fn); + // the log level hierarchy is determined by: + // - if RUST_LOG is detected at runtime + // - if RUST_LOG is provided at compile time + // - default to INFO let filter = EnvFilter::builder() - // set the default log level to INFO. - .with_default_directive(LevelFilter::INFO.into()) + .with_default_directive( + option_env!("RUST_LOG") + .unwrap_or("info") + .parse() + .expect("should provide valid log level at compile time."), + ) // parse directives from the RUST_LOG environment variable, // overriding the default directive for matching targets. .from_env_lossy(); From 16e4eb1dd0e9fb2ba748022e1f9b5a267fc910f9 Mon Sep 17 00:00:00 2001 From: Maximilian Power <mpower@bitwarden.com> Date: Mon, 17 Nov 2025 17:50:39 +0100 Subject: [PATCH 145/249] updates strings (#17422) * updated strings --- apps/web/src/locales/en/messages.json | 72 +++++++++---------- .../password-change-metric.component.html | 2 +- .../activity/all-activity.component.html | 2 +- .../assign-tasks-view.component.html | 2 +- .../new-applications-dialog.component.html | 6 +- .../risk-insights.component.html | 8 +-- .../risk-insights.component.ts | 20 ++---- ...risk-insights-drawer-dialog.component.html | 2 +- 8 files changed, 51 insertions(+), 63 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 23c430feedd..1b0460e2aa6 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9860,8 +9854,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html index ab59a36aa6a..fe9880724f3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.html @@ -12,7 +12,7 @@ </div> <div class="tw-items-baseline tw-gap-2"> - <span bitTypography="body2">{{ "onceYouReviewApps" | i18n }}</span> + <span bitTypography="body2">{{ "onceYouReviewApplications" | i18n }}</span> </div> } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index 43cf936e1a1..ffc67028b77 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -14,7 +14,7 @@ <dirt-activity-card [title]="'atRiskMembers' | i18n" [cardMetrics]="'membersAtRiskCount' | i18n: totalCriticalAppsAtRiskMemberCount" - [metricDescription]="'membersWithAccessToAtRiskItemsForCriticalApps' | i18n" + [metricDescription]="'membersWithAccessToAtRiskItemsForCriticalApplications' | i18n" actionText="{{ 'viewAtRiskMembers' | i18n }}" [showActionLink]="totalCriticalAppsAtRiskMemberCount > 0" (actionClick)="onViewAtRiskMembers()" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html index 859bc73905c..572918907f4 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html @@ -71,7 +71,7 @@ <!-- Description Text --> <div bitTypography="helper" class="tw-text-muted"> - {{ "membersWillReceiveNotification" | i18n }} + {{ "membersWillReceiveSecurityTask" | i18n }} </div> </div> </div> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html index 09fb5cb7ad9..04db5f6d521 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html @@ -5,7 +5,7 @@ ? hasNoCriticalApplications() ? ("prioritizeCriticalApplications" | i18n) : ("reviewNewApplications" | i18n) - : ("assignTasksToMembers" | i18n) + : ("assignSecurityTasksToMembers" | i18n) }} </span> @@ -15,8 +15,8 @@ <p bitTypography="body1" class="tw-mb-5"> {{ hasNoCriticalApplications() - ? ("selectCriticalApplicationsDescription" | i18n) - : ("reviewNewApplicationsDescription" | i18n) + ? ("selectCriticalAppsDescription" | i18n) + : ("reviewNewAppsDescription" | i18n) }} </p> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 5e00de853ff..19b655a8b23 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -16,8 +16,8 @@ <!-- Show Empty state when there are no applications (no ciphers to make reports on) --> <empty-state-card [videoSrc]="emptyStateVideoSrc" - [title]="this.i18nService.t('noApplicationsInOrgTitle', organizationName)" - [description]="this.i18nService.t('noApplicationsInOrgDescription')" + [title]="this.i18nService.t('noDataInOrgTitle')" + [description]="this.i18nService.t('noDataInOrgDescription')" [benefits]="emptyStateBenefits" [buttonText]="this.i18nService.t('importData')" [buttonIcon]="IMPORT_ICON" @@ -27,8 +27,8 @@ <!-- Show empty state for no reports run --> <empty-state-card [videoSrc]="emptyStateVideoSrc" - [title]="this.i18nService.t('noReportRunTitle')" - [description]="this.i18nService.t('noReportRunDescription')" + [title]="this.i18nService.t('noReportsRunTitle')" + [description]="this.i18nService.t('noReportsRunDescription')" [benefits]="emptyStateBenefits" [buttonText]="this.i18nService.t('riskInsightsRunReport')" [buttonIcon]="" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index eddc26cbc77..9e6901572c3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -10,7 +10,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, EMPTY, firstValueFrom } from "rxjs"; +import { EMPTY, firstValueFrom } from "rxjs"; import { distinctUntilChanged, map, tap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -84,14 +84,11 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { dataLastUpdated: Date | null = null; - // Empty state properties - protected organizationName = ""; - // Empty state computed properties protected emptyStateBenefits: [string, string][] = [ - [this.i18nService.t("benefit1Title"), this.i18nService.t("benefit1Description")], - [this.i18nService.t("benefit2Title"), this.i18nService.t("benefit2Description")], - [this.i18nService.t("benefit3Title"), this.i18nService.t("benefit3Description")], + [this.i18nService.t("feature1Title"), this.i18nService.t("feature1Description")], + [this.i18nService.t("feature2Title"), this.i18nService.t("feature2Description")], + [this.i18nService.t("feature3Title"), this.i18nService.t("feature3Description")], ]; protected emptyStateVideoSrc: string | null = "/videos/risk-insights-mark-as-critical.mp4"; @@ -140,17 +137,14 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { ) .subscribe(); - // Combine report data, vault items check, organization details, and generation state + // Subscribe to report data updates // This declarative pattern ensures proper cleanup and prevents memory leaks - combineLatest([this.dataService.enrichedReportData$, this.dataService.organizationDetails$]) + this.dataService.enrichedReportData$ .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(([report, orgDetails]) => { + .subscribe((report) => { // Update report state this.appsCount = report?.reportData.length ?? 0; this.dataLastUpdated = report?.creationDate ?? null; - - // Update organization name - this.organizationName = orgDetails?.organizationName ?? ""; }); // Subscribe to drawer state changes diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html index 87a8ee00e05..3fa72358f25 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/risk-insights-drawer-dialog.component.html @@ -8,7 +8,7 @@ <ng-container bitDialogContent> <span bitTypography="body1" class="tw-text-muted tw-text-sm">{{ (drawerDetails.atRiskMemberDetails?.length > 0 - ? "atRiskMembersDescription" + ? "atRiskMemberDescription" : "atRiskMembersDescriptionNone" ) | i18n }}</span> From 1fa10f24f706544227f224a348150cc28c9503cb Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:31:54 -0500 Subject: [PATCH 146/249] Fix beta artifact name (#17425) --- .github/workflows/build-desktop.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 98192ea9e08..102fbdbbdc8 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -871,6 +871,8 @@ jobs: -NewName bitwarden-beta-$env:_PACKAGE_VERSION-x64.nsis.7z Rename-Item -Path .\dist\nsis-web\Bitwarden-Beta-$env:_PACKAGE_VERSION-arm64.nsis.7z ` -NewName bitwarden-beta-$env:_PACKAGE_VERSION-arm64.nsis.7z + Rename-Item -Path .\dist\nsis-web\latest.yml ` + -NewName latest-beta.yml - name: Upload portable exe artifact uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 @@ -963,8 +965,8 @@ jobs: if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: - name: ${{ needs.setup.outputs.release_channel }}-beta.yml - path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml + name: latest-beta.yml + path: apps/desktop/dist/nsis-web/latest-beta.yml if-no-files-found: error macos-build: From b296750bcb272dea5197c9824ad68a916ea681c0 Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Mon, 17 Nov 2025 19:07:49 +0000 Subject: [PATCH 147/249] Bumped client version(s) --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index b95d3e6aba5..c2db376e5da 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2025.11.2", + "version": "2025.11.3", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index 46b70931f65..bb8355dc632 100644 --- a/package-lock.json +++ b/package-lock.json @@ -294,7 +294,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2025.11.2" + "version": "2025.11.3" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 4e2d8988f2aa1950f04b92d646bbbc727f0c1d3f Mon Sep 17 00:00:00 2001 From: Maximilian Power <mpower@bitwarden.com> Date: Mon, 17 Nov 2025 20:08:15 +0100 Subject: [PATCH 148/249] Update at-risk cards with accessibility improvements (#17427) --- .../all-applications.component.html | 111 +++++++++---- .../all-applications.component.ts | 6 +- .../critical-applications.component.html | 148 +++++++++++------- .../critical-applications.component.ts | 13 +- 4 files changed, 186 insertions(+), 92 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html index 26beaf349a9..b1c2faa4f05 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html @@ -5,42 +5,85 @@ <div class="tw-mt-4 tw-flex tw-flex-col"> <h2 class="tw-mb-6" bitTypography="h2">{{ "allApplications" | i18n }}</h2> <div class="tw-flex tw-gap-6"> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="dataService.setDrawerForOrgAtRiskMembers('allAppsOrgAtRiskMembers')" + <div + role="region" + [attr.aria-label]="'atRiskMembers' | i18n" + class="tw-flex-1 tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-rounded-lg tw-p-4" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers', + }" > - <dirt-card - #allAppsOrgAtRiskMembers - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskMembers', - }" - [title]="'atRiskMembers' | i18n" - [value]="applicationSummary.totalAtRiskMemberCount" - [maxValue]="applicationSummary.totalMemberCount" - > - </dirt-card> - </button> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="dataService.setDrawerForOrgAtRiskApps('allAppsOrgAtRiskApplications')" + <div class="tw-flex tw-flex-col tw-gap-1"> + <span bitTypography="h6" class="tw-flex tw-text-main" id="allAppsOrgAtRiskMembersLabel">{{ + "atRiskMembers" | i18n + }}</span> + <div class="tw-flex tw-items-baseline tw-gap-2" role="status" aria-live="polite"> + <span + bitTypography="h3" + class="!tw-mb-0" + aria-describedby="allAppsOrgAtRiskMembersLabel" + >{{ applicationSummary.totalAtRiskMemberCount }}</span + > + <span bitTypography="body2">{{ + "cardMetrics" | i18n: applicationSummary.totalMemberCount + }}</span> + </div> + <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> + <p bitTypography="body1" class="tw-mb-0"> + <button + type="button" + bitLink + [attr.aria-label]="('viewAtRiskMembers' | i18n) + ': ' + ('atRiskMembers' | i18n)" + (click)="dataService.setDrawerForOrgAtRiskMembers('allAppsOrgAtRiskMembers')" + > + {{ "viewAtRiskMembers" | i18n }} + </button> + </p> + </div> + </div> + </div> + <div + role="region" + [attr.aria-label]="'atRiskApplications' | i18n" + class="tw-flex-1 tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-rounded-lg tw-p-4" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications', + }" > - <dirt-card - #allAppsOrgAtRiskApplications - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'allAppsOrgAtRiskApplications', - }" - [title]="'atRiskApplications' | i18n" - [value]="applicationSummary.totalAtRiskApplicationCount" - [maxValue]="applicationSummary.totalApplicationCount" - > - </dirt-card> - </button> + <div class="tw-flex tw-flex-col tw-gap-1"> + <span + bitTypography="h6" + class="tw-flex tw-text-main" + id="allAppsOrgAtRiskApplicationsLabel" + >{{ "atRiskApplications" | i18n }}</span + > + <div class="tw-flex tw-items-baseline tw-gap-2" role="status" aria-live="polite"> + <span + bitTypography="h3" + class="!tw-mb-0" + aria-describedby="allAppsOrgAtRiskApplicationsLabel" + >{{ applicationSummary.totalAtRiskApplicationCount }}</span + > + <span bitTypography="body2">{{ + "cardMetrics" | i18n: applicationSummary.totalApplicationCount + }}</span> + </div> + <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> + <p bitTypography="body1" class="tw-mb-0"> + <button + type="button" + bitLink + [attr.aria-label]=" + ('viewAtRiskApplications' | i18n) + ': ' + ('atRiskApplications' | i18n) + " + (click)="dataService.setDrawerForOrgAtRiskApps('allAppsOrgAtRiskApplications')" + > + {{ "viewAtRiskApplications" | i18n }} + </button> + </p> + </div> + </div> + </div> </div> <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> <bit-search diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts index b4e2bf466b9..acad2901ba4 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts @@ -18,12 +18,13 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { IconButtonModule, + LinkModule, NoItemsModule, SearchModule, TableDataSource, ToastService, + TypographyModule, } from "@bitwarden/components"; -import { CardComponent } from "@bitwarden/dirt-card"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @@ -39,13 +40,14 @@ import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.co imports: [ ApplicationsLoadingComponent, HeaderModule, - CardComponent, + LinkModule, SearchModule, PipesModule, NoItemsModule, SharedModule, AppTableRowScrollableComponent, IconButtonModule, + TypographyModule, ], }) export class AllApplicationsComponent implements OnInit { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html index 04c7bd23797..332a91d28c1 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.html @@ -1,3 +1,4 @@ +@let drawerDetails = dataService.drawerDetails$ | async; <div class="tw-mt-4 tw-flex tw-flex-col"> <div class="tw-flex tw-justify-between tw-mb-4"> <h2 bitTypography="h2">{{ "criticalApplications" | i18n }}</h2> @@ -16,60 +17,101 @@ }} </button> </div> - @if (dataService.drawerDetails$ | async; as drawerDetails) { - <div class="tw-flex tw-gap-6"> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="dataService.setDrawerForCriticalAtRiskMembers('criticalAppsAtRiskMembers')" - > - <dirt-card - #criticalAppsAtRiskMembers - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskMembers', - }" - [title]="'atRiskMembers' | i18n" - [value]="applicationSummary.totalAtRiskMemberCount" - [maxValue]="applicationSummary.totalMemberCount" - > - </dirt-card> - </button> - <button - type="button" - class="tw-flex-1" - tabindex="0" - (click)="dataService.setDrawerForCriticalAtRiskApps('criticalAppsAtRiskApplications')" - > - <dirt-card - #criticalAppsAtRiskApplications - class="tw-w-full" - [ngClass]="{ - 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskApplications', - }" - [title]="'atRiskApplications' | i18n" - [value]="applicationSummary.totalAtRiskApplicationCount" - [maxValue]="applicationSummary.totalApplicationCount" - > - </dirt-card> - </button> + <div class="tw-flex tw-gap-6"> + <div + role="region" + [attr.aria-label]="'atRiskMembers' | i18n" + class="tw-flex-1 tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-rounded-lg tw-p-4" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskMembers', + }" + > + <div class="tw-flex tw-flex-col tw-gap-1"> + <span bitTypography="h6" class="tw-flex tw-text-main" id="criticalAppsAtRiskMembersLabel">{{ + "atRiskMembers" | i18n + }}</span> + <div class="tw-flex tw-items-baseline tw-gap-2" role="status" aria-live="polite"> + <span + bitTypography="h3" + class="!tw-mb-0" + aria-describedby="criticalAppsAtRiskMembersLabel" + >{{ applicationSummary.totalAtRiskMemberCount }}</span + > + <span bitTypography="body2">{{ + "cardMetrics" | i18n: applicationSummary.totalMemberCount + }}</span> + </div> + <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> + <p bitTypography="body1" class="tw-mb-0"> + <button + type="button" + bitLink + [attr.aria-label]="('viewAtRiskMembers' | i18n) + ': ' + ('atRiskMembers' | i18n)" + (click)="dataService.setDrawerForCriticalAtRiskMembers('criticalAppsAtRiskMembers')" + > + {{ "viewAtRiskMembers" | i18n }} + </button> + </p> + </div> + </div> </div> - <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> - <bit-search - [placeholder]="'searchApps' | i18n" - class="tw-grow" - [formControl]="searchControl" - ></bit-search> + <div + role="region" + [attr.aria-label]="'atRiskApplications' | i18n" + class="tw-flex-1 tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-rounded-lg tw-p-4" + [ngClass]="{ + 'tw-bg-primary-100': drawerDetails.invokerId === 'criticalAppsAtRiskApplications', + }" + > + <div class="tw-flex tw-flex-col tw-gap-1"> + <span + bitTypography="h6" + class="tw-flex tw-text-main" + id="criticalAppsAtRiskApplicationsLabel" + >{{ "atRiskApplications" | i18n }}</span + > + <div class="tw-flex tw-items-baseline tw-gap-2" role="status" aria-live="polite"> + <span + bitTypography="h3" + class="!tw-mb-0" + aria-describedby="criticalAppsAtRiskApplicationsLabel" + >{{ applicationSummary.totalAtRiskApplicationCount }}</span + > + <span bitTypography="body2">{{ + "cardMetrics" | i18n: applicationSummary.totalApplicationCount + }}</span> + </div> + <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> + <p bitTypography="body1" class="tw-mb-0"> + <button + type="button" + bitLink + [attr.aria-label]=" + ('viewAtRiskApplications' | i18n) + ': ' + ('atRiskApplications' | i18n) + " + (click)="dataService.setDrawerForCriticalAtRiskApps('criticalAppsAtRiskApplications')" + > + {{ "viewAtRiskApplications" | i18n }} + </button> + </p> + </div> + </div> </div> + </div> + <div class="tw-flex tw-mt-8 tw-mb-4 tw-gap-4"> + <bit-search + [placeholder]="'searchApps' | i18n" + class="tw-grow" + [formControl]="searchControl" + ></bit-search> + </div> - <app-table-row-scrollable - [dataSource]="dataSource" - [showRowCheckBox]="false" - [showRowMenuForCriticalApps]="true" - [openApplication]="drawerDetails.invokerId || ''" - [showAppAtRiskMembers]="showAppAtRiskMembers" - [unmarkAsCritical]="removeCriticalApplication" - ></app-table-row-scrollable> - } + <app-table-row-scrollable + [dataSource]="dataSource" + [showRowCheckBox]="false" + [showRowMenuForCriticalApps]="true" + [openApplication]="drawerDetails.invokerId || ''" + [showAppAtRiskMembers]="showAppAtRiskMembers" + [unmarkAsCritical]="removeCriticalApplication" + ></app-table-row-scrollable> </div> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts index 7b7ca8c42da..1ea745929db 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts @@ -17,8 +17,14 @@ import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-in import { OrganizationReportSummary } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { NoItemsModule, SearchModule, TableDataSource, ToastService } from "@bitwarden/components"; -import { CardComponent } from "@bitwarden/dirt-card"; +import { + LinkModule, + NoItemsModule, + SearchModule, + TableDataSource, + ToastService, + TypographyModule, +} from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @@ -33,13 +39,14 @@ import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks selector: "dirt-critical-applications", templateUrl: "./critical-applications.component.html", imports: [ - CardComponent, HeaderModule, + LinkModule, SearchModule, NoItemsModule, PipesModule, SharedModule, AppTableRowScrollableComponent, + TypographyModule, ], }) export class CriticalApplicationsComponent implements OnInit { From de17b33dd51236f4b01a0e93dfc4e2f6308ea10f Mon Sep 17 00:00:00 2001 From: SmithThe4th <gsmith@bitwarden.com> Date: Mon, 17 Nov 2025 15:10:50 -0500 Subject: [PATCH 149/249] handle empty strings in identity view for sdk cipher encryption (#17423) --- .../src/vault/models/view/identity.view.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index dca54fa04e8..fadfafb8777 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -94,16 +94,16 @@ export class IdentityView extends ItemView implements SdkIdentityView { this.lastName != null ) { let name = ""; - if (this.title != null) { + if (!Utils.isNullOrWhitespace(this.title)) { name += this.title + " "; } - if (this.firstName != null) { + if (!Utils.isNullOrWhitespace(this.firstName)) { name += this.firstName + " "; } - if (this.middleName != null) { + if (!Utils.isNullOrWhitespace(this.middleName)) { name += this.middleName + " "; } - if (this.lastName != null) { + if (!Utils.isNullOrWhitespace(this.lastName)) { name += this.lastName; } return name.trim(); @@ -130,14 +130,20 @@ export class IdentityView extends ItemView implements SdkIdentityView { } get fullAddressPart2(): string | undefined { - if (this.city == null && this.state == null && this.postalCode == null) { + const hasCity = !Utils.isNullOrWhitespace(this.city); + const hasState = !Utils.isNullOrWhitespace(this.state); + const hasPostalCode = !Utils.isNullOrWhitespace(this.postalCode); + + if (!hasCity && !hasState && !hasPostalCode) { return undefined; } - const city = this.city || "-"; + + const city = hasCity ? this.city : "-"; const state = this.state; - const postalCode = this.postalCode || "-"; + const postalCode = hasPostalCode ? this.postalCode : "-"; + let addressPart2 = city; - if (!Utils.isNullOrWhitespace(state)) { + if (hasState) { addressPart2 += ", " + state; } addressPart2 += ", " + postalCode; From 610537535a73c6b250de3c9287c13348b5a7756d Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:14:03 -0600 Subject: [PATCH 150/249] persist archive date when cloning a cipher (#16986) --- .../src/cipher-form/components/cipher-form.component.spec.ts | 4 ++-- .../vault/src/cipher-form/components/cipher-form.component.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts index a002956b54a..1e60ad91fb1 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts @@ -154,13 +154,13 @@ describe("CipherFormComponent", () => { expect(component["updatedCipherView"]?.login.fido2Credentials).toBeNull(); }); - it("clears archiveDate on updatedCipherView", async () => { + it("does not clear archiveDate on updatedCipherView", async () => { cipherView.archivedDate = new Date(); decryptCipher.mockResolvedValue(cipherView); await component.ngOnInit(); - expect(component["updatedCipherView"]?.archivedDate).toBeNull(); + expect(component["updatedCipherView"]?.archivedDate).toBe(cipherView.archivedDate); }); }); diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 5e75ea5bc24..f94af25e90a 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -281,7 +281,6 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci if (this.config.mode === "clone") { this.updatedCipherView.id = null; - this.updatedCipherView.archivedDate = null; if (this.updatedCipherView.login) { this.updatedCipherView.login.fido2Credentials = null; From 670f3514baabf87ac01497df68ebee920f21b594 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:36:37 -0800 Subject: [PATCH 151/249] [PM-23384] - Browser extension spotlight directing to Premium signup in web (#17343) * premium upgrade nudge * add specs * clean up vault template and specs * fix date comparison. add more specs for date * fix spec * fix specs * make prop private --- apps/browser/src/_locales/en/messages.json | 9 + .../vault-v2/vault-v2.component.html | 9 + .../vault-v2/vault-v2.component.spec.ts | 564 ++++++++++++++++++ .../components/vault-v2/vault-v2.component.ts | 48 ++ .../services/vault-popup-items.service.ts | 5 + .../src/vault/services/nudges.service.ts | 1 + 6 files changed, 636 insertions(+) create mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2384cf8c4ec..f793b24a0e9 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 5bca9cddd4f..faaa6a40e98 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -30,6 +30,15 @@ <!-- Show search & filters outside of the scroll area of the page --> <ng-container slot="above-scroll-area"> + <ng-container *ngIf="showPremiumSpotlight$ | async"> + <bit-spotlight + [title]="'upgradeCompleteSecurity' | i18n" + [subtitle]="'premiumGivesMoreTools' | i18n" + [buttonText]="'explorePremium' | i18n" + (onButtonClick)="showPremiumDialog()" + (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.PremiumUpgrade)" + ></bit-spotlight> + </ng-container> <ng-container *ngIf="vaultState === VaultStateEnum.Empty && showEmptyVaultSpotlight$ | async"> <bit-spotlight [title]="'emptyVaultNudgeTitle' | i18n" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts new file mode 100644 index 00000000000..563ec5f9709 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts @@ -0,0 +1,564 @@ +import { CdkVirtualScrollableElement } from "@angular/cdk/scrolling"; +import { ChangeDetectionStrategy, Component, input, NO_ERRORS_SCHEMA } from "@angular/core"; +import { TestBed, fakeAsync, flush, tick } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { ActivatedRoute, Router } from "@angular/router"; +import { RouterTestingModule } from "@angular/router/testing"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject, Observable, Subject, of } from "rxjs"; + +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; +import { NudgeType, NudgesService } from "@bitwarden/angular/vault"; +import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; +import { CurrentAccountComponent } from "@bitwarden/browser/auth/popup/account-switching/current-account.component"; +import AutofillService from "@bitwarden/browser/autofill/services/autofill.service"; +import { PopOutComponent } from "@bitwarden/browser/platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "@bitwarden/browser/platform/popup/layout/popup-header.component"; +import { PopupRouterCacheService } from "@bitwarden/browser/platform/popup/view-cache/popup-router-cache.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; +import { TaskService } from "@bitwarden/common/vault/tasks"; +import { DialogService } from "@bitwarden/components"; +import { StateProvider } from "@bitwarden/state"; +import { DecryptionFailureDialogComponent } from "@bitwarden/vault"; + +import { BrowserApi } from "../../../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils"; +import { IntroCarouselService } from "../../services/intro-carousel.service"; +import { VaultPopupAutofillService } from "../../services/vault-popup-autofill.service"; +import { VaultPopupCopyButtonsService } from "../../services/vault-popup-copy-buttons.service"; +import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; +import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; +import { VaultPopupScrollPositionService } from "../../services/vault-popup-scroll-position.service"; +import { AtRiskPasswordCalloutComponent } from "../at-risk-callout/at-risk-password-callout.component"; + +import { AutofillVaultListItemsComponent } from "./autofill-vault-list-items/autofill-vault-list-items.component"; +import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; +import { NewItemDropdownV2Component } from "./new-item-dropdown/new-item-dropdown-v2.component"; +import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; +import { VaultListItemsContainerComponent } from "./vault-list-items-container/vault-list-items-container.component"; +import { VaultV2Component } from "./vault-v2.component"; + +@Component({ + selector: "popup-header", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PopupHeaderStubComponent { + readonly pageTitle = input(""); +} + +@Component({ + selector: "app-vault-header-v2", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VaultHeaderV2StubComponent {} + +@Component({ + selector: "app-current-account", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class CurrentAccountStubComponent {} + +@Component({ + selector: "app-new-item-dropdown", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class NewItemDropdownStubComponent { + readonly initialValues = input(); +} + +@Component({ + selector: "app-pop-out", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class PopOutStubComponent {} + +@Component({ + selector: "blocked-injection-banner", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class BlockedInjectionBannerStubComponent {} + +@Component({ + selector: "vault-at-risk-password-callout", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class VaultAtRiskCalloutStubComponent {} + +@Component({ + selector: "app-autofill-vault-list-items", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class AutofillVaultListItemsStubComponent {} + +@Component({ + selector: "app-vault-list-items-container", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class VaultListItemsContainerStubComponent { + readonly title = input<string>(); + readonly ciphers = input<any[]>(); + readonly id = input<string>(); + readonly disableSectionMargin = input<boolean>(); + readonly collapsibleKey = input<string>(); +} + +const mockDialogRef = { + close: jest.fn(), + afterClosed: jest.fn().mockReturnValue(of(undefined)), +} as unknown as import("@bitwarden/components").DialogRef<any, any>; + +jest + .spyOn(PremiumUpgradeDialogComponent, "open") + .mockImplementation((_: DialogService) => mockDialogRef as any); + +jest + .spyOn(DecryptionFailureDialogComponent, "open") + .mockImplementation((_: DialogService, _params: any) => mockDialogRef as any); +jest.spyOn(BrowserApi, "isPopupOpen").mockResolvedValue(false); +jest.spyOn(BrowserPopupUtils, "openCurrentPagePopout").mockResolvedValue(); + +describe("VaultV2Component", () => { + let component: VaultV2Component; + + interface FakeAccount { + id: string; + } + + function queryAllSpotlights(fixture: any): HTMLElement[] { + return Array.from(fixture.nativeElement.querySelectorAll("bit-spotlight")) as HTMLElement[]; + } + + const itemsSvc: any = { + emptyVault$: new BehaviorSubject<boolean>(false), + noFilteredResults$: new BehaviorSubject<boolean>(false), + showDeactivatedOrg$: new BehaviorSubject<boolean>(false), + favoriteCiphers$: new BehaviorSubject<any[]>([]), + remainingCiphers$: new BehaviorSubject<any[]>([]), + cipherCount$: new BehaviorSubject<number>(0), + loading$: new BehaviorSubject<boolean>(true), + } as Partial<VaultPopupItemsService>; + + const filtersSvc = { + allFilters$: new Subject<any>(), + filters$: new BehaviorSubject<any>({}), + filterVisibilityState$: new BehaviorSubject<any>({}), + } as Partial<VaultPopupListFiltersService>; + + const accountActive$ = new BehaviorSubject<FakeAccount | null>({ id: "user-1" }); + + const cipherSvc = { + failedToDecryptCiphers$: jest.fn().mockReturnValue(of([])), + } as Partial<CipherService>; + + const nudgesSvc = { + showNudgeSpotlight$: jest.fn().mockImplementation((_type: NudgeType) => of(false)), + dismissNudge: jest.fn().mockResolvedValue(undefined), + } as Partial<NudgesService>; + + const dialogSvc = {} as Partial<DialogService>; + + const introSvc = { + setIntroCarouselDismissed: jest.fn().mockResolvedValue(undefined), + } as Partial<IntroCarouselService>; + + const scrollSvc = { + start: jest.fn(), + stop: jest.fn(), + } as Partial<VaultPopupScrollPositionService>; + + function getObs<T = unknown>(cmp: any, key: string): Observable<T> { + return cmp[key] as Observable<T>; + } + + const hasPremiumFromAnySource$ = new BehaviorSubject<boolean>(false); + + const billingSvc = { + hasPremiumFromAnySource$: (_: string) => hasPremiumFromAnySource$, + }; + + const vaultProfileSvc = { + getProfileCreationDate: jest + .fn() + .mockResolvedValue(new Date(Date.now() - 8 * 24 * 60 * 60 * 1000)), // 8 days ago + }; + + beforeEach(async () => { + jest.clearAllMocks(); + await TestBed.configureTestingModule({ + imports: [VaultV2Component, RouterTestingModule], + providers: [ + { provide: VaultPopupItemsService, useValue: itemsSvc }, + { provide: VaultPopupListFiltersService, useValue: filtersSvc }, + { provide: VaultPopupScrollPositionService, useValue: scrollSvc }, + { + provide: AccountService, + useValue: { activeAccount$: accountActive$ }, + }, + { provide: CipherService, useValue: cipherSvc }, + { provide: DialogService, useValue: dialogSvc }, + { provide: IntroCarouselService, useValue: introSvc }, + { provide: NudgesService, useValue: nudgesSvc }, + { + provide: VaultProfileService, + useValue: vaultProfileSvc, + }, + { + provide: VaultPopupCopyButtonsService, + useValue: { showQuickCopyActions$: new BehaviorSubject<boolean>(false) }, + }, + { + provide: BillingAccountProfileStateService, + useValue: billingSvc, + }, + { + provide: I18nService, + useValue: { translate: (key: string) => key, t: (key: string) => key }, + }, + { provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() }, + { provide: RestrictedItemTypesService, useValue: { restricted$: new BehaviorSubject([]) } }, + { provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() }, + { provide: AvatarService, useValue: mock<AvatarService>() }, + { provide: ActivatedRoute, useValue: mock<ActivatedRoute>() }, + { provide: AuthService, useValue: mock<AuthService>() }, + { provide: AutofillService, useValue: mock<AutofillService>() }, + { + provide: VaultPopupAutofillService, + useValue: mock<VaultPopupAutofillService>(), + }, + { provide: TaskService, useValue: mock<TaskService>() }, + { provide: StateProvider, useValue: mock<StateProvider>() }, + { + provide: ConfigService, + useValue: { + getFeatureFlag$: (_: string) => of(false), + }, + }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + + TestBed.overrideComponent(VaultV2Component, { + remove: { + imports: [ + PopupHeaderComponent, + VaultHeaderV2Component, + CurrentAccountComponent, + NewItemDropdownV2Component, + PopOutComponent, + BlockedInjectionBanner, + AtRiskPasswordCalloutComponent, + AutofillVaultListItemsComponent, + VaultListItemsContainerComponent, + ], + }, + add: { + imports: [ + PopupHeaderStubComponent, + VaultHeaderV2StubComponent, + CurrentAccountStubComponent, + NewItemDropdownStubComponent, + PopOutStubComponent, + BlockedInjectionBannerStubComponent, + VaultAtRiskCalloutStubComponent, + AutofillVaultListItemsStubComponent, + VaultListItemsContainerStubComponent, + ], + }, + }); + + const fixture = TestBed.createComponent(VaultV2Component); + component = fixture.componentInstance; + }); + + describe("vaultState", () => { + type ExpectedKey = "Empty" | "DeactivatedOrg" | "NoResults" | null; + + const cases: [string, boolean, boolean, boolean, ExpectedKey][] = [ + ["null when none true", false, false, false, null], + ["Empty when empty true only", true, false, false, "Empty"], + ["DeactivatedOrg when only deactivated true", false, false, true, "DeactivatedOrg"], + ["NoResults when only noResults true", false, true, false, "NoResults"], + ]; + + it.each(cases)( + "%s", + fakeAsync( + ( + _label: string, + empty: boolean, + noResults: boolean, + deactivated: boolean, + expectedKey: ExpectedKey, + ) => { + const empty$ = itemsSvc.emptyVault$ as BehaviorSubject<boolean>; + const noResults$ = itemsSvc.noFilteredResults$ as BehaviorSubject<boolean>; + const deactivated$ = itemsSvc.showDeactivatedOrg$ as BehaviorSubject<boolean>; + + empty$.next(empty); + noResults$.next(noResults); + deactivated$.next(deactivated); + tick(); + + const expectedValue = + expectedKey === null ? null : (component as any).VaultStateEnum[expectedKey]; + + expect((component as any).vaultState).toBe(expectedValue); + }, + ), + ); + }); + + it("loading$ is true when items loading or filters missing; false when both ready", () => { + const itemsLoading$ = itemsSvc.loading$ as unknown as BehaviorSubject<boolean>; + const allFilters$ = filtersSvc.allFilters$ as unknown as Subject<any>; + + const values: boolean[] = []; + getObs<boolean>(component, "loading$").subscribe((v) => values.push(!!v)); + + itemsLoading$.next(true); + + allFilters$.next({}); + + itemsLoading$.next(false); + + expect(values[values.length - 1]).toBe(false); + }); + + it("ngAfterViewInit waits for allFilters$ then starts scroll position service", fakeAsync(() => { + const allFilters$ = filtersSvc.allFilters$ as unknown as Subject<any>; + + (component as any).virtualScrollElement = {} as CdkVirtualScrollableElement; + + component.ngAfterViewInit(); + expect(scrollSvc.start).not.toHaveBeenCalled(); + + allFilters$.next({ any: true }); + tick(); + + expect(scrollSvc.start).toHaveBeenCalledTimes(1); + expect(scrollSvc.start).toHaveBeenCalledWith((component as any).virtualScrollElement); + + flush(); + })); + + it("showPremiumDialog opens PremiumUpgradeDialogComponent", () => { + component["showPremiumDialog"](); + expect(PremiumUpgradeDialogComponent.open).toHaveBeenCalledTimes(1); + }); + + it("navigateToImport navigates and opens popout if popup is open", fakeAsync(async () => { + (BrowserApi.isPopupOpen as jest.Mock).mockResolvedValueOnce(true); + + const ngRouter = TestBed.inject(Router); + jest.spyOn(ngRouter, "navigate").mockResolvedValue(true as any); + + await component["navigateToImport"](); + + expect(ngRouter.navigate).toHaveBeenCalledWith(["/import"]); + + expect(BrowserPopupUtils.openCurrentPagePopout).toHaveBeenCalled(); + })); + + it("navigateToImport does not popout when popup is not open", fakeAsync(async () => { + (BrowserApi.isPopupOpen as jest.Mock).mockResolvedValueOnce(false); + + const ngRouter = TestBed.inject(Router); + jest.spyOn(ngRouter, "navigate").mockResolvedValue(true as any); + + await component["navigateToImport"](); + + expect(ngRouter.navigate).toHaveBeenCalledWith(["/import"]); + expect(BrowserPopupUtils.openCurrentPagePopout).not.toHaveBeenCalled(); + })); + + it("ngOnInit dismisses intro carousel and opens decryption dialog for non-deleted failures", fakeAsync(() => { + (cipherSvc.failedToDecryptCiphers$ as any).mockReturnValue( + of([ + { id: "a", isDeleted: false }, + { id: "b", isDeleted: true }, + { id: "c", isDeleted: false }, + ]), + ); + + void component.ngOnInit(); + tick(); + + expect(introSvc.setIntroCarouselDismissed).toHaveBeenCalled(); + + expect(DecryptionFailureDialogComponent.open).toHaveBeenCalledWith(expect.any(Object), { + cipherIds: ["a", "c"], + }); + + flush(); + })); + + it("dismissVaultNudgeSpotlight forwards to NudgesService with active user id", fakeAsync(() => { + const spy = jest.spyOn(nudgesSvc, "dismissNudge").mockResolvedValue(undefined); + + accountActive$.next({ id: "user-xyz" }); + + void component.ngOnInit(); + tick(); + + void component["dismissVaultNudgeSpotlight"](NudgeType.HasVaultItems); + tick(); + + expect(spy).toHaveBeenCalledWith(NudgeType.HasVaultItems, "user-xyz"); + })); + + it("accountAgeInDays$ computes integer days since creation", (done) => { + getObs<number | null>(component, "accountAgeInDays$").subscribe((days) => { + if (days !== null) { + expect(days).toBeGreaterThanOrEqual(7); + done(); + } + }); + + void component.ngOnInit(); + }); + + it("renders Premium spotlight when eligible and opens dialog on click", fakeAsync(() => { + itemsSvc.cipherCount$.next(10); + + hasPremiumFromAnySource$.next(false); + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => + of(type === NudgeType.PremiumUpgrade), + ); + + const fixture = TestBed.createComponent(VaultV2Component); + const component = fixture.componentInstance; + + void component.ngOnInit(); + + fixture.detectChanges(); + tick(); + + fixture.detectChanges(); + + const spotlights = Array.from( + fixture.nativeElement.querySelectorAll("bit-spotlight"), + ) as HTMLElement[]; + expect(spotlights.length).toBe(1); + + const spotDe = fixture.debugElement.query(By.css("bit-spotlight")); + expect(spotDe).toBeTruthy(); + + spotDe.triggerEventHandler("onButtonClick", undefined); + fixture.detectChanges(); + + expect(PremiumUpgradeDialogComponent.open).toHaveBeenCalledTimes(1); + })); + + it("renders Empty-Vault spotlight when vaultState is Empty and nudge is on", fakeAsync(() => { + itemsSvc.emptyVault$.next(true); + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + return of(type === NudgeType.EmptyVaultNudge); + }); + + const fixture = TestBed.createComponent(VaultV2Component); + fixture.detectChanges(); + tick(); + + const spotlights = queryAllSpotlights(fixture); + expect(spotlights.length).toBe(1); + + expect(fixture.nativeElement.textContent).toContain("emptyVaultNudgeTitle"); + })); + + it("renders Has-Items spotlight when vault has items and nudge is on", fakeAsync(() => { + itemsSvc.emptyVault$.next(false); + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + return of(type === NudgeType.HasVaultItems); + }); + + const fixture = TestBed.createComponent(VaultV2Component); + fixture.detectChanges(); + tick(); + + const spotlights = queryAllSpotlights(fixture); + expect(spotlights.length).toBe(1); + + expect(fixture.nativeElement.textContent).toContain("hasItemsVaultNudgeTitle"); + })); + + it("does not render Premium spotlight when account is less than a week old", fakeAsync(() => { + itemsSvc.cipherCount$.next(10); + hasPremiumFromAnySource$.next(false); + + vaultProfileSvc.getProfileCreationDate = jest + .fn() + .mockResolvedValue(new Date(Date.now() - 3 * 24 * 60 * 60 * 1000)); // 3 days ago + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + return of(type === NudgeType.PremiumUpgrade); + }); + + const fixture = TestBed.createComponent(VaultV2Component); + fixture.detectChanges(); + tick(); + + const spotlights = queryAllSpotlights(fixture); + expect(spotlights.length).toBe(0); + })); + + it("does not render Premium spotlight when vault has less than 5 items", fakeAsync(() => { + itemsSvc.cipherCount$.next(3); + hasPremiumFromAnySource$.next(false); + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + return of(type === NudgeType.PremiumUpgrade); + }); + + const fixture = TestBed.createComponent(VaultV2Component); + fixture.detectChanges(); + tick(); + + const spotlights = queryAllSpotlights(fixture); + expect(spotlights.length).toBe(0); + })); + + it("does not render Premium spotlight when user already has premium", fakeAsync(() => { + itemsSvc.cipherCount$.next(10); + hasPremiumFromAnySource$.next(true); + + (nudgesSvc.showNudgeSpotlight$ as jest.Mock).mockImplementation((type: NudgeType) => { + return of(type === NudgeType.PremiumUpgrade); + }); + + const fixture = TestBed.createComponent(VaultV2Component); + fixture.detectChanges(); + tick(); + + const spotlights = queryAllSpotlights(fixture); + expect(spotlights.length).toBe(0); + })); +}); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index e55a702d350..499e9b76757 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -9,6 +9,7 @@ import { distinctUntilChanged, filter, firstValueFrom, + from, map, Observable, shareReplay, @@ -17,12 +18,15 @@ import { tap, } 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 { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; import { DeactivatedOrg, NoResults, VaultOpen } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -124,13 +128,55 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { void this.liveAnnouncer.announce(this.i18nService.translate(key), "polite"); }), ); + private skeletonFeatureFlag$ = this.configService.getFeatureFlag$( FeatureFlag.VaultLoadingSkeletons, ); + private showPremiumNudgeSpotlight$ = this.activeUserId$.pipe( + switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.PremiumUpgrade, userId)), + ); + protected favoriteCiphers$ = this.vaultPopupItemsService.favoriteCiphers$; protected remainingCiphers$ = this.vaultPopupItemsService.remainingCiphers$; protected allFilters$ = this.vaultPopupListFiltersService.allFilters$; + protected cipherCount$ = this.vaultPopupItemsService.cipherCount$; + protected hasPremium$ = this.activeUserId$.pipe( + switchMap((userId) => this.billingAccountService.hasPremiumFromAnySource$(userId)), + ); + protected accountAgeInDays$ = this.activeUserId$.pipe( + switchMap((userId) => { + const creationDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)); + return creationDate$.pipe( + map((creationDate) => { + if (!creationDate) { + return 0; + } + const ageInMilliseconds = Date.now() - creationDate.getTime(); + return Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24)); + }), + ); + }), + ); + + protected showPremiumSpotlight$ = combineLatest([ + this.showPremiumNudgeSpotlight$, + this.showEmptyVaultSpotlight$, + this.showHasItemsVaultSpotlight$, + this.hasPremium$, + this.cipherCount$, + this.accountAgeInDays$, + ]).pipe( + map( + ([showNudge, emptyVault, hasItems, hasPremium, count, age]) => + showNudge && !emptyVault && !hasItems && !hasPremium && count >= 5 && age >= 7, + ), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + showPremiumDialog() { + PremiumUpgradeDialogComponent.open(this.dialogService); + } /** When true, show spinner loading state */ protected showSpinnerLoaders$ = combineLatest([this.loading$, this.skeletonFeatureFlag$]).pipe( @@ -177,6 +223,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private introCarouselService: IntroCarouselService, private nudgesService: NudgesService, private router: Router, + private vaultProfileService: VaultProfileService, + private billingAccountService: BillingAccountProfileStateService, private liveAnnouncer: LiveAnnouncer, private i18nService: I18nService, private configService: ConfigService, diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index afe9d61d5af..321d7936806 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -288,6 +288,11 @@ export class VaultPopupItemsService { map((ciphers) => !ciphers.length), ); + /** + * Observable that contains the count of ciphers in the active filtered list. + */ + cipherCount$: Observable<number> = this._activeCipherList$.pipe(map((ciphers) => ciphers.length)); + /** * Observable that indicates whether there are no ciphers to show with the current filter. */ diff --git a/libs/angular/src/vault/services/nudges.service.ts b/libs/angular/src/vault/services/nudges.service.ts index 4c77ff38bf6..05d565eb499 100644 --- a/libs/angular/src/vault/services/nudges.service.ts +++ b/libs/angular/src/vault/services/nudges.service.ts @@ -37,6 +37,7 @@ export const NudgeType = { NewNoteItemStatus: "new-note-item-status", NewSshItemStatus: "new-ssh-item-status", GeneratorNudgeStatus: "generator-nudge-status", + PremiumUpgrade: "premium-upgrade", } as const; export type NudgeType = UnionOfValues<typeof NudgeType>; From 413a024e61935b49e2b272ff8789ce2f7029dd51 Mon Sep 17 00:00:00 2001 From: aj-bw <81774843+aj-bw@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:33:12 -0500 Subject: [PATCH 152/249] removal of freebsd build, upload, release and other references (#17354) --- .github/workflows/build-desktop.yml | 7 ------- .github/workflows/release-desktop.yml | 1 - apps/desktop/desktop_native/napi/index.js | 6 ------ apps/desktop/electron-builder.json | 5 +---- 4 files changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 102fbdbbdc8..27a7bfe4124 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -270,13 +270,6 @@ jobs: path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - - name: Upload .freebsd artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 - with: - name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd - path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd - if-no-files-found: error - - name: Upload .snap artifact uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 53132d8647c..10a0f581faa 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -107,7 +107,6 @@ jobs: with: artifacts: "apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-amd64.deb, apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x86_64.rpm, - apps/desktop/artifacts/Bitwarden-${{ env.PKG_VERSION }}-x64.freebsd, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_amd64.snap, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.snap, apps/desktop/artifacts/bitwarden_${{ env.PKG_VERSION }}_arm64.tar.gz, diff --git a/apps/desktop/desktop_native/napi/index.js b/apps/desktop/desktop_native/napi/index.js index acfd0dffb89..64819be4405 100644 --- a/apps/desktop/desktop_native/napi/index.js +++ b/apps/desktop/desktop_native/napi/index.js @@ -78,12 +78,6 @@ switch (platform) { throw new Error(`Unsupported architecture on macOS: ${arch}`); } break; - case "freebsd": - nativeBinding = loadFirstAvailable( - ["desktop_napi.freebsd-x64.node"], - "@bitwarden/desktop-napi-freebsd-x64", - ); - break; case "linux": switch (arch) { case "x64": diff --git a/apps/desktop/electron-builder.json b/apps/desktop/electron-builder.json index eaa299db508..6e89799e9c4 100644 --- a/apps/desktop/electron-builder.json +++ b/apps/desktop/electron-builder.json @@ -116,7 +116,7 @@ "to": "libprocess_isolation.so" } ], - "target": ["deb", "freebsd", "rpm", "AppImage", "snap"], + "target": ["deb", "rpm", "AppImage", "snap"], "desktop": { "entry": { "Name": "Bitwarden", @@ -252,9 +252,6 @@ "artifactName": "${productName}-${version}-${arch}.${ext}", "fpm": ["--rpm-rpmbuild-define", "_build_id_links none"] }, - "freebsd": { - "artifactName": "${productName}-${version}-${arch}.${ext}" - }, "snap": { "summary": "Bitwarden is a secure and free password manager for all of your devices.", "description": "Password Manager\n**Installation**\nBitwarden requires access to the `password-manager-service`. Please enable it through permissions or by running `sudo snap connect bitwarden:password-manager-service` after installation. See https://btwrdn.com/install-snap for details.", From 8f04f258180a3ccf4dad13d480c35345adbec1d8 Mon Sep 17 00:00:00 2001 From: Maximilian Power <mpower@bitwarden.com> Date: Tue, 18 Nov 2025 09:37:31 +0100 Subject: [PATCH 153/249] Fix Firefox phishing blocker continue button by awaiting tab navigation promises (#17436) --- apps/browser/src/platform/browser/browser-api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index 723df95ef63..cfc39fa18a1 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -268,11 +268,11 @@ export class BrowserApi { static async closeTab(tabId: number): Promise<void> { if (tabId) { if (BrowserApi.isWebExtensionsApi) { - browser.tabs.remove(tabId).catch((error) => { + await browser.tabs.remove(tabId).catch((error) => { throw new Error("[BrowserApi] Failed to remove current tab: " + error.message); }); } else if (BrowserApi.isChromeApi) { - chrome.tabs.remove(tabId).catch((error) => { + await chrome.tabs.remove(tabId).catch((error) => { throw new Error("[BrowserApi] Failed to remove current tab: " + error.message); }); } @@ -288,7 +288,7 @@ export class BrowserApi { static async navigateTabToUrl(tabId: number, url: URL): Promise<void> { if (tabId) { if (BrowserApi.isWebExtensionsApi) { - browser.tabs.update(tabId, { url: url.href }).catch((error) => { + await browser.tabs.update(tabId, { url: url.href }).catch((error) => { throw new Error("Failed to navigate tab to URL: " + error.message); }); } else if (BrowserApi.isChromeApi) { From 9efc31534ba36c627d699661609a84e8a783875c Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 18 Nov 2025 13:26:38 +0100 Subject: [PATCH 154/249] [PM-28231] Enable component-class-suffix (#17384) * Enable component-class-suffix * Rename file --- .../pages/phishing-warning.component.ts | 2 ++ .../at-risk-passwords.component.spec.ts | 2 ++ .../assign-collections.component.ts | 2 ++ .../blocked-injection-banner.component.ts | 2 ++ .../vault-generator-dialog.component.spec.ts | 2 ++ .../adjust-subscription.component.ts | 2 ++ .../vertical-step.component.ts | 2 ++ .../app/layouts/header/web-header.stories.ts | 8 ++++---- .../web-generator-dialog.component.spec.ts | 2 ++ .../individual-vault/add-edit-v2.component.ts | 2 ++ eslint.config.mjs | 2 +- .../src/button/button.component.spec.ts | 10 +++++----- .../chip-select/chip-select.component.spec.ts | 8 ++++---- .../src/chip-select/chip-select.component.ts | 4 ++-- .../src/dialog/dialog.service.stories.ts | 4 ++-- .../simple-dialog.service.stories.ts | 12 ++++++------ .../src/form-control/form-control.module.ts | 6 +++--- .../src/form-control/label.component.ts | 2 +- .../src/form-field/error-summary.component.ts | 2 +- .../src/form-field/form-field.component.ts | 4 ++-- .../src/form-field/form-field.module.ts | 6 +++--- libs/components/src/menu/index.ts | 2 +- ...em.directive.ts => menu-item.component.ts} | 2 +- .../src/menu/menu.component.spec.ts | 8 ++++---- libs/components/src/menu/menu.component.ts | 6 +++--- libs/components/src/menu/menu.module.ts | 6 +++--- .../radio-group.component.spec.ts | 10 +++++----- .../src/radio-button/radio-group.component.ts | 4 ++-- .../components/kitchen-sink-form.component.ts | 2 +- .../components/kitchen-sink-main.component.ts | 19 ++++++++++++------- .../kitchen-sink-table.component.ts | 2 +- .../kitchen-sink-toggle-list.component.ts | 2 +- .../kitchen-sink/kitchen-sink.stories.ts | 12 ++++++------ .../src/switch/switch.component.spec.ts | 6 +++--- .../components/src/switch/switch.component.ts | 4 ++-- .../toggle-group.component.spec.ts | 10 +++++----- .../copy-cipher-field.directive.spec.ts | 8 ++++---- .../components/copy-cipher-field.directive.ts | 10 +++++----- 38 files changed, 111 insertions(+), 88 deletions(-) rename libs/components/src/menu/{menu-item.directive.ts => menu-item.component.ts} (96%) diff --git a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts index 2b91a28122c..589b880b206 100644 --- a/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts +++ b/apps/browser/src/dirt/phishing-detection/pages/phishing-warning.component.ts @@ -47,6 +47,8 @@ import { TypographyModule, ], }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class PhishingWarning { private activatedRoute = inject(ActivatedRoute); private messageSender = inject(MessageSender); diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts index 96c597113a5..9b972fd0f3e 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.spec.ts @@ -61,6 +61,8 @@ class MockPopupPageComponent { template: `<ng-content></ng-content>`, changeDetection: ChangeDetectionStrategy.OnPush, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix class MockAppIcon { readonly cipher = input<CipherView | undefined>(undefined); } diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index b314c48fecd..546a352111e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -49,6 +49,8 @@ import { PopupPageComponent } from "../../../../../platform/popup/layout/popup-p PopOutComponent, ], }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class AssignCollections { /** Params needed to populate the assign collections component */ params: CollectionAssignmentParams; diff --git a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts index 2125af289a2..44a033137de 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/blocked-injection-banner/blocked-injection-banner.component.ts @@ -30,6 +30,8 @@ const blockedURISettingsRoute = "/blocked-domains"; selector: "blocked-injection-banner", templateUrl: "blocked-injection-banner.component.html", }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class BlockedInjectionBanner implements OnInit { /** * Flag indicating that the banner should be shown diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts index 2139b6d9a4f..8fa48dc5d79 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.spec.ts @@ -24,6 +24,8 @@ import { selector: "vault-cipher-form-generator", template: "", }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix class MockCipherFormGenerator { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 7ee5891e8a9..255e1ef544c 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -23,6 +23,8 @@ import { ToastService } from "@bitwarden/components"; templateUrl: "adjust-subscription.component.html", standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class AdjustSubscription implements OnInit, OnDestroy { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts index efd0f68e5d1..f79bb5a4451 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts @@ -9,6 +9,8 @@ import { Component, Input } from "@angular/core"; providers: [{ provide: CdkStep, useExisting: VerticalStep }], standalone: false, }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class VerticalStep extends CdkStep { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals diff --git a/apps/web/src/app/layouts/header/web-header.stories.ts b/apps/web/src/app/layouts/header/web-header.stories.ts index ccc68cf5db9..88c98f01e6c 100644 --- a/apps/web/src/app/layouts/header/web-header.stories.ts +++ b/apps/web/src/app/layouts/header/web-header.stories.ts @@ -53,7 +53,7 @@ class MockStateService { template: `<button type="button" bitIconButton="bwi-filter" label="Switch products"></button>`, standalone: false, }) -class MockProductSwitcher {} +class MockProductSwitcherComponent {} // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -62,7 +62,7 @@ class MockProductSwitcher {} template: `<bit-avatar [text]="name$ | async"></bit-avatar>`, imports: [CommonModule, AvatarModule], }) -class MockDynamicAvatar implements Partial<DynamicAvatarComponent> { +class MockDynamicAvatarComponent implements Partial<DynamicAvatarComponent> { protected name$ = combineLatest([ this.stateService.accounts$, this.stateService.activeAccount$, @@ -100,9 +100,9 @@ export default { TabsModule, TypographyModule, NavigationModule, - MockDynamicAvatar, + MockDynamicAvatarComponent, ], - declarations: [WebHeaderComponent, MockProductSwitcher], + declarations: [WebHeaderComponent, MockProductSwitcherComponent], providers: [ { provide: StateService, useClass: MockStateService }, { diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts index c2d6c87d865..82eb1180961 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.spec.ts @@ -22,6 +22,8 @@ import { selector: "vault-cipher-form-generator", template: "", }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix class MockCipherFormGenerator { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index 41c922cf4fe..5277d4a0aea 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -78,6 +78,8 @@ export interface AddEditCipherDialogCloseResult { ], providers: [{ provide: CipherFormGenerationService, useClass: WebCipherFormGenerationService }], }) +// FIXME(https://bitwarden.atlassian.net/browse/PM-28231): Use Component suffix +// eslint-disable-next-line @angular-eslint/component-class-suffix export class AddEditComponentV2 implements OnInit { config: CipherFormConfig; headerText: string; diff --git a/eslint.config.mjs b/eslint.config.mjs index bfa8b6ec079..df98b3af424 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -61,7 +61,7 @@ export default tseslint.config( "rxjs/no-exposed-subjects": ["error", { allowProtected: true }], // TODO: Enable these. - "@angular-eslint/component-class-suffix": 0, + "@angular-eslint/component-class-suffix": "error", "@angular-eslint/contextual-lifecycle": "error", "@angular-eslint/directive-class-suffix": 0, "@angular-eslint/no-empty-lifecycle-method": 0, diff --git a/libs/components/src/button/button.component.spec.ts b/libs/components/src/button/button.component.spec.ts index 745be014a0c..4eb92227d22 100644 --- a/libs/components/src/button/button.component.spec.ts +++ b/libs/components/src/button/button.component.spec.ts @@ -5,19 +5,19 @@ import { By } from "@angular/platform-browser"; import { ButtonModule } from "./index"; describe("Button", () => { - let fixture: ComponentFixture<TestApp>; - let testAppComponent: TestApp; + let fixture: ComponentFixture<TestAppComponent>; + let testAppComponent: TestAppComponent; let buttonDebugElement: DebugElement; let disabledButtonDebugElement: DebugElement; let linkDebugElement: DebugElement; beforeEach(async () => { TestBed.configureTestingModule({ - imports: [TestApp], + imports: [TestAppComponent], }); await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(TestAppComponent); testAppComponent = fixture.debugElement.componentInstance; buttonDebugElement = fixture.debugElement.query(By.css("button")); disabledButtonDebugElement = fixture.debugElement.query(By.css("button#disabled")); @@ -86,7 +86,7 @@ describe("Button", () => { `, imports: [ButtonModule], }) -class TestApp { +class TestAppComponent { buttonType?: string; block?: boolean; disabled?: boolean; diff --git a/libs/components/src/chip-select/chip-select.component.spec.ts b/libs/components/src/chip-select/chip-select.component.spec.ts index 2485a5bca7e..3c2f71ef8d7 100644 --- a/libs/components/src/chip-select/chip-select.component.spec.ts +++ b/libs/components/src/chip-select/chip-select.component.spec.ts @@ -27,7 +27,7 @@ const mockI18nService = { describe("ChipSelectComponent", () => { let component: ChipSelectComponent<string>; - let fixture: ComponentFixture<TestApp>; + let fixture: ComponentFixture<TestAppComponent>; const testOptions: ChipSelectOption<string>[] = [ { label: "Option 1", value: "opt1", icon: "bwi-folder" }, @@ -58,11 +58,11 @@ describe("ChipSelectComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [TestApp, NoopAnimationsModule], + imports: [TestAppComponent, NoopAnimationsModule], providers: [{ provide: I18nService, useValue: mockI18nService }], }).compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(TestAppComponent); fixture.detectChanges(); component = fixture.debugElement.query(By.directive(ChipSelectComponent)).componentInstance; @@ -468,7 +468,7 @@ describe("ChipSelectComponent", () => { imports: [ChipSelectComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) -class TestApp { +class TestAppComponent { readonly options = signal<ChipSelectOption<string>[]>([ { label: "Option 1", value: "opt1", icon: "bwi-folder" }, { label: "Option 2", value: "opt2" }, diff --git a/libs/components/src/chip-select/chip-select.component.ts b/libs/components/src/chip-select/chip-select.component.ts index e0f8b343040..bf6c6fb2aad 100644 --- a/libs/components/src/chip-select/chip-select.component.ts +++ b/libs/components/src/chip-select/chip-select.component.ts @@ -20,7 +20,7 @@ import { compareValues } from "@bitwarden/common/platform/misc/compare-values"; import { ButtonModule } from "../button"; import { IconButtonModule } from "../icon-button"; -import { MenuComponent, MenuItemDirective, MenuModule, MenuTriggerForDirective } from "../menu"; +import { MenuComponent, MenuItemComponent, MenuModule, MenuTriggerForDirective } from "../menu"; import { Option } from "../select/option"; import { SharedModule } from "../shared"; import { TypographyModule } from "../typography"; @@ -51,7 +51,7 @@ export class ChipSelectComponent<T = unknown> implements ControlValueAccessor { private readonly cdr = inject(ChangeDetectorRef); readonly menu = viewChild(MenuComponent); - readonly menuItems = viewChildren(MenuItemDirective); + readonly menuItems = viewChildren(MenuItemComponent); readonly chipSelectButton = viewChild<ElementRef<HTMLButtonElement>>("chipSelectButton"); readonly menuTrigger = viewChild(MenuTriggerForDirective); diff --git a/libs/components/src/dialog/dialog.service.stories.ts b/libs/components/src/dialog/dialog.service.stories.ts index 9bcb704a7fd..0921e9a9def 100644 --- a/libs/components/src/dialog/dialog.service.stories.ts +++ b/libs/components/src/dialog/dialog.service.stories.ts @@ -47,7 +47,7 @@ class StoryDialogComponent { } openDialogNonDismissable() { - this.dialogService.open(NonDismissableContent, { + this.dialogService.open(NonDismissableContentComponent, { data: { animal: "panda", }, @@ -117,7 +117,7 @@ class StoryDialogContentComponent { `, imports: [DialogModule, ButtonModule], }) -class NonDismissableContent { +class NonDismissableContentComponent { constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: Animal, diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts index b682b9f772a..7e03ad60ca2 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.service.stories.ts @@ -33,7 +33,7 @@ class StoryDialogComponent { constructor(public dialogService: DialogService) {} openSimpleDialog() { - this.dialogService.open(SimpleDialogContent, { + this.dialogService.open(SimpleDialogContentComponent, { data: { animal: "panda", }, @@ -42,7 +42,7 @@ class StoryDialogComponent { } openNonDismissableWithPrimaryButtonDialog() { - this.dialogService.open(NonDismissableWithPrimaryButtonContent, { + this.dialogService.open(NonDismissableWithPrimaryButtonContentComponent, { data: { animal: "panda", }, @@ -52,7 +52,7 @@ class StoryDialogComponent { } openNonDismissableWithNoButtonsDialog() { - this.dialogService.open(NonDismissableWithNoButtonsContent, { + this.dialogService.open(NonDismissableWithNoButtonsContentComponent, { data: { animal: "panda", }, @@ -83,7 +83,7 @@ class StoryDialogComponent { `, imports: [ButtonModule, DialogModule], }) -class SimpleDialogContent { +class SimpleDialogContentComponent { constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: Animal, @@ -114,7 +114,7 @@ class SimpleDialogContent { `, imports: [ButtonModule, DialogModule], }) -class NonDismissableWithPrimaryButtonContent { +class NonDismissableWithPrimaryButtonContentComponent { constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: Animal, @@ -140,7 +140,7 @@ class NonDismissableWithPrimaryButtonContent { `, imports: [ButtonModule, DialogModule], }) -class NonDismissableWithNoButtonsContent { +class NonDismissableWithNoButtonsContentComponent { constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: Animal, diff --git a/libs/components/src/form-control/form-control.module.ts b/libs/components/src/form-control/form-control.module.ts index df168d8e98f..2646f36ecd3 100644 --- a/libs/components/src/form-control/form-control.module.ts +++ b/libs/components/src/form-control/form-control.module.ts @@ -2,10 +2,10 @@ import { NgModule } from "@angular/core"; import { FormControlComponent } from "./form-control.component"; import { BitHintComponent } from "./hint.component"; -import { BitLabel } from "./label.component"; +import { BitLabelComponent } from "./label.component"; @NgModule({ - imports: [BitLabel, FormControlComponent, BitHintComponent], - exports: [FormControlComponent, BitLabel, BitHintComponent], + imports: [BitLabelComponent, FormControlComponent, BitHintComponent], + exports: [FormControlComponent, BitLabelComponent, BitHintComponent], }) export class FormControlModule {} diff --git a/libs/components/src/form-control/label.component.ts b/libs/components/src/form-control/label.component.ts index 57f9e338bb6..95133cae99f 100644 --- a/libs/components/src/form-control/label.component.ts +++ b/libs/components/src/form-control/label.component.ts @@ -17,7 +17,7 @@ let nextId = 0; "[id]": "id()", }, }) -export class BitLabel { +export class BitLabelComponent { constructor( private elementRef: ElementRef<HTMLInputElement>, @Optional() private parentFormControl: FormControlComponent, diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index cf26fd1e21f..cbcd172399a 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -16,7 +16,7 @@ import { I18nPipe } from "@bitwarden/ui-common"; }, imports: [I18nPipe], }) -export class BitErrorSummary { +export class BitErrorSummaryComponent { readonly formGroup = input<UntypedFormGroup>(); get errorCount(): number { diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index ccfdcec4132..3d49a58b1fc 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -16,7 +16,7 @@ import { import { I18nPipe } from "@bitwarden/ui-common"; import { BitHintComponent } from "../form-control/hint.component"; -import { BitLabel } from "../form-control/label.component"; +import { BitLabelComponent } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; import { BitErrorComponent } from "./error.component"; @@ -32,7 +32,7 @@ import { BitFormFieldControl } from "./form-field-control"; export class BitFormFieldComponent implements AfterContentChecked { readonly input = contentChild.required(BitFormFieldControl); readonly hint = contentChild(BitHintComponent); - readonly label = contentChild(BitLabel); + readonly label = contentChild(BitLabelComponent); readonly prefixContainer = viewChild<ElementRef<HTMLDivElement>>("prefixContainer"); readonly suffixContainer = viewChild<ElementRef<HTMLDivElement>>("suffixContainer"); diff --git a/libs/components/src/form-field/form-field.module.ts b/libs/components/src/form-field/form-field.module.ts index 88d7ffcc78b..c7529c14fb7 100644 --- a/libs/components/src/form-field/form-field.module.ts +++ b/libs/components/src/form-field/form-field.module.ts @@ -4,7 +4,7 @@ import { FormControlModule } from "../form-control"; import { InputModule } from "../input/input.module"; import { MultiSelectModule } from "../multi-select/multi-select.module"; -import { BitErrorSummary } from "./error-summary.component"; +import { BitErrorSummaryComponent } from "./error-summary.component"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldComponent } from "./form-field.component"; import { BitPasswordInputToggleDirective } from "./password-input-toggle.directive"; @@ -18,7 +18,7 @@ import { BitSuffixDirective } from "./suffix.directive"; MultiSelectModule, BitErrorComponent, - BitErrorSummary, + BitErrorSummaryComponent, BitFormFieldComponent, BitPasswordInputToggleDirective, BitPrefixDirective, @@ -30,7 +30,7 @@ import { BitSuffixDirective } from "./suffix.directive"; MultiSelectModule, BitErrorComponent, - BitErrorSummary, + BitErrorSummaryComponent, BitFormFieldComponent, BitPasswordInputToggleDirective, BitPrefixDirective, diff --git a/libs/components/src/menu/index.ts b/libs/components/src/menu/index.ts index 52c48d2fb8e..69b71b8ee24 100644 --- a/libs/components/src/menu/index.ts +++ b/libs/components/src/menu/index.ts @@ -1,5 +1,5 @@ export * from "./menu.module"; export * from "./menu.component"; export * from "./menu-trigger-for.directive"; -export * from "./menu-item.directive"; +export * from "./menu-item.component"; export * from "./menu-divider.component"; diff --git a/libs/components/src/menu/menu-item.directive.ts b/libs/components/src/menu/menu-item.component.ts similarity index 96% rename from libs/components/src/menu/menu-item.directive.ts rename to libs/components/src/menu/menu-item.component.ts index e19e208f7b0..149fc3ca297 100644 --- a/libs/components/src/menu/menu-item.directive.ts +++ b/libs/components/src/menu/menu-item.component.ts @@ -10,7 +10,7 @@ import { Component, ElementRef, HostBinding, Input } from "@angular/core"; templateUrl: "menu-item.component.html", imports: [NgClass], }) -export class MenuItemDirective implements FocusableOption { +export class MenuItemComponent implements FocusableOption { @HostBinding("class") classList = [ "tw-block", "tw-w-full", diff --git a/libs/components/src/menu/menu.component.spec.ts b/libs/components/src/menu/menu.component.spec.ts index 59ad7c6dbc8..5ec614afade 100644 --- a/libs/components/src/menu/menu.component.spec.ts +++ b/libs/components/src/menu/menu.component.spec.ts @@ -7,7 +7,7 @@ import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; import { MenuModule } from "./index"; describe("Menu", () => { - let fixture: ComponentFixture<TestApp>; + let fixture: ComponentFixture<TestAppComponent>; const getMenuTriggerDirective = () => { const buttonDebugElement = fixture.debugElement.query(By.directive(MenuTriggerForDirective)); return buttonDebugElement.injector.get(MenuTriggerForDirective); @@ -18,12 +18,12 @@ describe("Menu", () => { beforeEach(async () => { TestBed.configureTestingModule({ - imports: [TestApp], + imports: [TestAppComponent], }); await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(TestAppComponent); fixture.detectChanges(); }); @@ -82,4 +82,4 @@ describe("Menu", () => { `, imports: [MenuModule], }) -class TestApp {} +class TestAppComponent {} diff --git a/libs/components/src/menu/menu.component.ts b/libs/components/src/menu/menu.component.ts index f32a790ef69..fc7a4673fea 100644 --- a/libs/components/src/menu/menu.component.ts +++ b/libs/components/src/menu/menu.component.ts @@ -10,7 +10,7 @@ import { contentChildren, } from "@angular/core"; -import { MenuItemDirective } from "./menu-item.directive"; +import { MenuItemComponent } from "./menu-item.component"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -25,8 +25,8 @@ export class MenuComponent implements AfterContentInit { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref @Output() closed = new EventEmitter<void>(); - readonly menuItems = contentChildren(MenuItemDirective, { descendants: true }); - keyManager?: FocusKeyManager<MenuItemDirective>; + readonly menuItems = contentChildren(MenuItemComponent, { descendants: true }); + keyManager?: FocusKeyManager<MenuItemComponent>; readonly ariaRole = input<"menu" | "dialog">("menu"); diff --git a/libs/components/src/menu/menu.module.ts b/libs/components/src/menu/menu.module.ts index 117460df559..a9a5e64dd9f 100644 --- a/libs/components/src/menu/menu.module.ts +++ b/libs/components/src/menu/menu.module.ts @@ -1,12 +1,12 @@ import { NgModule } from "@angular/core"; import { MenuDividerComponent } from "./menu-divider.component"; -import { MenuItemDirective } from "./menu-item.directive"; +import { MenuItemComponent } from "./menu-item.component"; import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; import { MenuComponent } from "./menu.component"; @NgModule({ - imports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], - exports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], + imports: [MenuComponent, MenuTriggerForDirective, MenuItemComponent, MenuDividerComponent], + exports: [MenuComponent, MenuTriggerForDirective, MenuItemComponent, MenuDividerComponent], }) export class MenuModule {} diff --git a/libs/components/src/radio-button/radio-group.component.spec.ts b/libs/components/src/radio-button/radio-group.component.spec.ts index 94cc7bc25cb..8d70dde9a56 100644 --- a/libs/components/src/radio-button/radio-group.component.spec.ts +++ b/libs/components/src/radio-button/radio-group.component.spec.ts @@ -11,19 +11,19 @@ import { RadioButtonComponent } from "./radio-button.component"; import { RadioButtonModule } from "./radio-button.module"; describe("RadioGroupComponent", () => { - let fixture: ComponentFixture<TestApp>; - let testAppComponent: TestApp; + let fixture: ComponentFixture<TestAppComponent>; + let testAppComponent: TestAppComponent; let buttonElements: RadioButtonComponent[]; let radioButtons: HTMLInputElement[]; beforeEach(async () => { TestBed.configureTestingModule({ - imports: [TestApp], + imports: [TestAppComponent], providers: [{ provide: I18nService, useValue: new I18nMockService({}) }], }); await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(TestAppComponent); fixture.detectChanges(); testAppComponent = fixture.debugElement.componentInstance; buttonElements = fixture.debugElement @@ -76,6 +76,6 @@ describe("RadioGroupComponent", () => { `, imports: [FormsModule, RadioButtonModule], }) -class TestApp { +class TestAppComponent { selected?: string; } diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index dd44623b313..7a1288b71e5 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -4,7 +4,7 @@ import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; import { I18nPipe } from "@bitwarden/ui-common"; -import { BitLabel } from "../form-control/label.component"; +import { BitLabelComponent } from "../form-control/label.component"; let nextId = 0; @@ -32,7 +32,7 @@ export class RadioGroupComponent implements ControlValueAccessor { readonly id = input(`bit-radio-group-${nextId++}`); @HostBinding("class") classList = ["tw-block", "tw-mb-4"]; - protected readonly label = contentChild(BitLabel); + protected readonly label = contentChild(BitLabelComponent); constructor(@Optional() @Self() private ngControl?: NgControl) { if (ngControl != null) { diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts index 8f49415e0dd..0a9581fb375 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts @@ -134,7 +134,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; </form> `, }) -export class KitchenSinkForm { +export class KitchenSinkFormComponent { constructor( public dialogService: DialogService, public formBuilder: FormBuilder, diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index f96217ffb63..0819ae3f06b 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -4,9 +4,9 @@ import { Component, signal } from "@angular/core"; import { DialogService } from "../../../dialog"; import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; -import { KitchenSinkForm } from "./kitchen-sink-form.component"; -import { KitchenSinkTable } from "./kitchen-sink-table.component"; -import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; +import { KitchenSinkFormComponent } from "./kitchen-sink-form.component"; +import { KitchenSinkTableComponent } from "./kitchen-sink-table.component"; +import { KitchenSinkToggleListComponent } from "./kitchen-sink-toggle-list.component"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -83,7 +83,7 @@ import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; </bit-dialog> `, }) -class KitchenSinkDialog { +class KitchenSinkDialogComponent { constructor(public dialogRef: DialogRef) {} } @@ -91,7 +91,12 @@ class KitchenSinkDialog { // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "bit-tab-main", - imports: [KitchenSinkSharedModule, KitchenSinkTable, KitchenSinkToggleList, KitchenSinkForm], + imports: [ + KitchenSinkSharedModule, + KitchenSinkTableComponent, + KitchenSinkToggleListComponent, + KitchenSinkFormComponent, + ], template: ` <bit-banner bannerType="info"> Kitchen Sink test zone </bit-banner> @@ -182,11 +187,11 @@ export class KitchenSinkMainComponent { protected readonly drawerOpen = signal(false); openDialog() { - this.dialogService.open(KitchenSinkDialog); + this.dialogService.open(KitchenSinkDialogComponent); } openDrawer() { - this.dialogService.openDrawer(KitchenSinkDialog); + this.dialogService.openDrawer(KitchenSinkDialogComponent); } navItems = [ diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts index d8d2e20f9ae..69602100eae 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-table.component.ts @@ -57,4 +57,4 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; </bit-table> `, }) -export class KitchenSinkTable {} +export class KitchenSinkTableComponent {} diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts index 18846ce2831..f914c49cca5 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-toggle-list.component.ts @@ -28,7 +28,7 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module"; } `, }) -export class KitchenSinkToggleList { +export class KitchenSinkToggleListComponent { selectedToggle: "all" | "large" | "small" = "all"; companyList = [ diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index a0bcbbffe04..c25eb778d11 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -19,10 +19,10 @@ import { I18nMockService } from "../../utils/i18n-mock.service"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component"; -import { KitchenSinkForm } from "./components/kitchen-sink-form.component"; +import { KitchenSinkFormComponent } from "./components/kitchen-sink-form.component"; import { KitchenSinkMainComponent } from "./components/kitchen-sink-main.component"; -import { KitchenSinkTable } from "./components/kitchen-sink-table.component"; -import { KitchenSinkToggleList } from "./components/kitchen-sink-toggle-list.component"; +import { KitchenSinkTableComponent } from "./components/kitchen-sink-table.component"; +import { KitchenSinkToggleListComponent } from "./components/kitchen-sink-toggle-list.component"; import { KitchenSinkSharedModule } from "./kitchen-sink-shared.module"; export default { @@ -33,10 +33,10 @@ export default { moduleMetadata({ imports: [ KitchenSinkSharedModule, - KitchenSinkForm, + KitchenSinkFormComponent, KitchenSinkMainComponent, - KitchenSinkTable, - KitchenSinkToggleList, + KitchenSinkTableComponent, + KitchenSinkToggleListComponent, ], }), applicationConfig({ diff --git a/libs/components/src/switch/switch.component.spec.ts b/libs/components/src/switch/switch.component.spec.ts index 10574c8084e..6a53316a1b4 100644 --- a/libs/components/src/switch/switch.component.spec.ts +++ b/libs/components/src/switch/switch.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from "@angular/forms"; import { By } from "@angular/platform-browser"; -import { BitLabel } from "../form-control/label.component"; +import { BitLabelComponent } from "../form-control/label.component"; import { SwitchComponent } from "./switch.component"; @@ -16,7 +16,7 @@ describe("SwitchComponent", () => { // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ selector: "test-host", - imports: [FormsModule, BitLabel, ReactiveFormsModule, SwitchComponent], + imports: [FormsModule, BitLabelComponent, ReactiveFormsModule, SwitchComponent], template: ` <form [formGroup]="formObj"> <bit-switch formControlName="switch"> @@ -77,7 +77,7 @@ describe("SwitchComponent", () => { selector: "test-selected-host", template: `<bit-switch [selected]="checked"><bit-label>Element</bit-label></bit-switch>`, standalone: true, - imports: [SwitchComponent, BitLabel], + imports: [SwitchComponent, BitLabelComponent], }) class TestSelectedHostComponent { checked = false; diff --git a/libs/components/src/switch/switch.component.ts b/libs/components/src/switch/switch.component.ts index 456fe5671b1..30e1ac59d48 100644 --- a/libs/components/src/switch/switch.component.ts +++ b/libs/components/src/switch/switch.component.ts @@ -14,7 +14,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 { BitLabel } from "../form-control/label.component"; +import { BitLabelComponent } from "../form-control/label.component"; let nextId = 0; @@ -43,7 +43,7 @@ let nextId = 0; }) export class SwitchComponent implements ControlValueAccessor, AfterViewInit { private el = inject(ElementRef<HTMLButtonElement>); - private readonly label = contentChild.required(BitLabel); + private readonly label = contentChild.required(BitLabelComponent); /** * Model signal for selected state binding when used outside of a form diff --git a/libs/components/src/toggle-group/toggle-group.component.spec.ts b/libs/components/src/toggle-group/toggle-group.component.spec.ts index 832741ae79c..3a9d35b862b 100644 --- a/libs/components/src/toggle-group/toggle-group.component.spec.ts +++ b/libs/components/src/toggle-group/toggle-group.component.spec.ts @@ -6,18 +6,18 @@ import { ToggleGroupModule } from "./toggle-group.module"; import { ToggleComponent } from "./toggle.component"; describe("Button", () => { - let fixture: ComponentFixture<TestApp>; - let testAppComponent: TestApp; + let fixture: ComponentFixture<TestAppComponent>; + let testAppComponent: TestAppComponent; let buttonElements: ToggleComponent<unknown>[]; let radioButtons: HTMLInputElement[]; beforeEach(async () => { TestBed.configureTestingModule({ - imports: [TestApp], + imports: [TestAppComponent], }); await TestBed.compileComponents(); - fixture = TestBed.createComponent(TestApp); + fixture = TestBed.createComponent(TestAppComponent); testAppComponent = fixture.debugElement.componentInstance; buttonElements = fixture.debugElement .queryAll(By.css("bit-toggle")) @@ -66,6 +66,6 @@ describe("Button", () => { `, imports: [ToggleGroupModule], }) -class TestApp { +class TestAppComponent { selected?: string; } diff --git a/libs/vault/src/components/copy-cipher-field.directive.spec.ts b/libs/vault/src/components/copy-cipher-field.directive.spec.ts index a3650c68c9b..e20fccd053f 100644 --- a/libs/vault/src/components/copy-cipher-field.directive.spec.ts +++ b/libs/vault/src/components/copy-cipher-field.directive.spec.ts @@ -5,7 +5,7 @@ import { Account, AccountService } from "@bitwarden/common/auth/abstractions/acc import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { BitIconButtonComponent, MenuItemDirective } from "@bitwarden/components"; +import { BitIconButtonComponent, MenuItemComponent } from "@bitwarden/components"; import { CopyCipherFieldService } from "@bitwarden/vault"; import { CopyCipherFieldDirective } from "./copy-cipher-field.directive"; @@ -83,7 +83,7 @@ describe("CopyCipherFieldDirective", () => { }); it("updates menuItemDirective disabled state", async () => { - const menuItemDirective = { + const menuItemComponent = { disabled: false, }; @@ -91,14 +91,14 @@ describe("CopyCipherFieldDirective", () => { copyFieldService as unknown as CopyCipherFieldService, mockAccountService, mockCipherService, - menuItemDirective as unknown as MenuItemDirective, + menuItemComponent as unknown as MenuItemComponent, ); copyCipherFieldDirective.action = "totp"; await copyCipherFieldDirective.ngOnChanges(); - expect(menuItemDirective.disabled).toBe(true); + expect(menuItemComponent.disabled).toBe(true); }); }); diff --git a/libs/vault/src/components/copy-cipher-field.directive.ts b/libs/vault/src/components/copy-cipher-field.directive.ts index 7e8ca334f9e..b4b1941273f 100644 --- a/libs/vault/src/components/copy-cipher-field.directive.ts +++ b/libs/vault/src/components/copy-cipher-field.directive.ts @@ -10,7 +10,7 @@ import { CipherViewLike, CipherViewLikeUtils, } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; -import { MenuItemDirective, BitIconButtonComponent } from "@bitwarden/components"; +import { MenuItemComponent, BitIconButtonComponent } from "@bitwarden/components"; import { CopyAction, CopyCipherFieldService } from "@bitwarden/vault"; /** @@ -47,7 +47,7 @@ export class CopyCipherFieldDirective implements OnChanges { private copyCipherFieldService: CopyCipherFieldService, private accountService: AccountService, private cipherService: CipherService, - @Optional() private menuItemDirective?: MenuItemDirective, + @Optional() private menuItemComponent?: MenuItemComponent, @Optional() private iconButtonComponent?: BitIconButtonComponent, ) {} @@ -60,7 +60,7 @@ export class CopyCipherFieldDirective implements OnChanges { */ @HostBinding("class.tw-hidden") private get hidden() { - return this.disabled && this.menuItemDirective; + return this.disabled && this.menuItemComponent; } @HostListener("click") @@ -87,8 +87,8 @@ export class CopyCipherFieldDirective implements OnChanges { } // If the directive is used on a menu item, update the menu item to prevent keyboard navigation - if (this.menuItemDirective) { - this.menuItemDirective.disabled = this.disabled ?? false; + if (this.menuItemComponent) { + this.menuItemComponent.disabled = this.disabled ?? false; } } From 2bf734bd433701d8eeeedf60ca9328ef31d1deb3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 08:48:29 -0600 Subject: [PATCH 155/249] [deps] Platform: Update @types/node to v22.19.1 (#17448) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index a4286aabed9..9ad1ffb3ec0 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -19,7 +19,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.0", + "@types/node": "22.19.1", "typescript": "5.4.2" } }, @@ -117,9 +117,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", - "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "license": "MIT", "peer": true, "dependencies": { diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 55699af47dd..21a6ba3626a 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -24,7 +24,7 @@ "yargs": "18.0.0" }, "devDependencies": { - "@types/node": "22.19.0", + "@types/node": "22.19.1", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/package-lock.json b/package-lock.json index bb8355dc632..82d05df83d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,7 +112,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.19.0", + "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.16", @@ -14391,9 +14391,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", - "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", + "version": "22.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", + "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/package.json b/package.json index f9757aa1e68..53fcb74ce80 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.19.0", + "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.16", From bd2f6e75666f1604fbb3554d14c13496e4cb0de4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:49:45 +0100 Subject: [PATCH 156/249] [deps]: Update anchore/scan-action action to v7 (#16635) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-web.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 719063958f7..89dd684c5f0 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -334,7 +334,7 @@ jobs: - name: Scan Docker image if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan - uses: anchore/scan-action@1638637db639e0ade3258b51db49a9a137574c3e # v6.5.1 + uses: anchore/scan-action@568b89d27fc18c60e56937bff480c91c772cd993 # v7.1.0 with: image: ${{ steps.image-name.outputs.name }} fail-build: false From 20d44b5136f20d061bb3756b9f114757e74fad03 Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:03:05 -0600 Subject: [PATCH 157/249] [PM-27739] Fix bug for icons not showing on application tables (#17373) * Added function to get a cipher icon for application tables. Update all application component to use signal properties * Fix type * Handle no ciphers on application --- .../risk-insights-orchestrator.service.ts | 18 ++++ .../view/risk-insights-data.service.ts | 4 +- .../all-applications.component.html | 16 ++-- .../all-applications.component.ts | 87 ++++++++++++------- .../critical-applications.component.ts | 34 ++++++-- .../app-table-row-scrollable.component.html | 5 +- .../app-table-row-scrollable.component.ts | 7 +- 7 files changed, 117 insertions(+), 54 deletions(-) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts index 38e12373182..51d35570cd2 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-orchestrator.service.ts @@ -35,6 +35,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { LogService } from "@bitwarden/logging"; import { @@ -191,6 +192,23 @@ export class RiskInsightsOrchestratorService { this._generateReportTriggerSubject.next(true); } + /** + * Gets the cipher icon for a given cipher ID + * + * @param cipherId The ID of the cipher to get the icon for + * @returns A CipherViewLike if found, otherwise undefined + */ + getCipherIcon(cipherId: string): CipherViewLike | undefined { + const currentCiphers = this._ciphersSubject.value; + if (!currentCiphers) { + return undefined; + } + + const foundCipher = currentCiphers.find((c) => c.id === cipherId); + + return foundCipher; + } + /** * Initializes the service context for a specific organization * diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts index 7b9255ca821..d426a6b09c1 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/view/risk-insights-data.service.ts @@ -87,7 +87,9 @@ export class RiskInsightsDataService { this._destroy$.complete(); } - // ----- UI-triggered methods (delegate to orchestrator) ----- + getCipherIcon(cipherId: string) { + return this.orchestrator.getCipherIcon(cipherId); + } initializeForOrganization(organizationId: OrganizationId) { this.orchestrator.initializeForOrganization(organizationId); } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html index b1c2faa4f05..73ded40388d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.html @@ -22,10 +22,10 @@ bitTypography="h3" class="!tw-mb-0" aria-describedby="allAppsOrgAtRiskMembersLabel" - >{{ applicationSummary.totalAtRiskMemberCount }}</span + >{{ applicationSummary().totalAtRiskMemberCount }}</span > <span bitTypography="body2">{{ - "cardMetrics" | i18n: applicationSummary.totalMemberCount + "cardMetrics" | i18n: applicationSummary().totalMemberCount }}</span> </div> <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> @@ -62,10 +62,10 @@ bitTypography="h3" class="!tw-mb-0" aria-describedby="allAppsOrgAtRiskApplicationsLabel" - >{{ applicationSummary.totalAtRiskApplicationCount }}</span + >{{ applicationSummary().totalAtRiskApplicationCount }}</span > <span bitTypography="body2">{{ - "cardMetrics" | i18n: applicationSummary.totalApplicationCount + "cardMetrics" | i18n: applicationSummary().totalApplicationCount }}</span> </div> <div class="tw-flex tw-items-baseline tw-mt-1 tw-gap-2"> @@ -95,11 +95,11 @@ type="button" [buttonType]="'primary'" bitButton - [disabled]="!selectedUrls.size" - [loading]="markingAsCritical" + [disabled]="!selectedUrls().size" + [loading]="markingAsCritical()" (click)="markAppsAsCritical()" > - <i class="bwi tw-mr-2" [ngClass]="selectedUrls.size ? 'bwi-star-f' : 'bwi-star'"></i> + <i class="bwi tw-mr-2" [ngClass]="selectedUrls().size ? 'bwi-star-f' : 'bwi-star'"></i> {{ "markAppAsCritical" | i18n }} </button> </div> @@ -108,7 +108,7 @@ [dataSource]="dataSource" [showRowCheckBox]="true" [showRowMenuForCriticalApps]="false" - [selectedUrls]="selectedUrls" + [selectedUrls]="selectedUrls()" [openApplication]="drawerDetails.invokerId || ''" [checkboxChange]="onCheckboxChange" [showAppAtRiskMembers]="showAppAtRiskMembers" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts index acad2901ba4..3a9159ad68c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications/all-applications.component.ts @@ -1,20 +1,23 @@ -import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { + Component, + DestroyRef, + inject, + OnInit, + ChangeDetectionStrategy, + signal, +} from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; +import { ActivatedRoute } from "@angular/router"; import { debounceTime } from "rxjs"; import { Security } from "@bitwarden/assets/svg"; -import { - ApplicationHealthReportDetailEnriched, - RiskInsightsDataService, -} from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { createNewSummaryData } from "@bitwarden/bit-common/dirt/reports/risk-insights/helpers"; import { OrganizationReportSummary, ReportStatus, } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/report-models"; -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { IconButtonModule, @@ -29,12 +32,14 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; -import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component"; +import { + ApplicationTableDataSource, + AppTableRowScrollableComponent, +} from "../shared/app-table-row-scrollable.component"; import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.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({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "dirt-all-applications", templateUrl: "./all-applications.component.html", imports: [ @@ -51,24 +56,25 @@ import { ApplicationsLoadingComponent } from "../shared/risk-insights-loading.co ], }) export class AllApplicationsComponent implements OnInit { - protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>(); - protected selectedUrls: Set<string> = new Set<string>(); - protected searchControl = new FormControl("", { nonNullable: true }); - protected organization = new Organization(); - noItemsIcon = Security; - protected markingAsCritical = false; - protected applicationSummary: OrganizationReportSummary = createNewSummaryData(); - protected ReportStatusEnum = ReportStatus; - destroyRef = inject(DestroyRef); + protected ReportStatusEnum = ReportStatus; + protected noItemsIcon = Security; + + // Standard properties + protected readonly dataSource = new TableDataSource<ApplicationTableDataSource>(); + protected readonly searchControl = new FormControl("", { nonNullable: true }); + + // Template driven properties + protected readonly selectedUrls = signal(new Set<string>()); + protected readonly markingAsCritical = signal(false); + protected readonly applicationSummary = signal<OrganizationReportSummary>(createNewSummaryData()); + constructor( protected i18nService: I18nService, protected activatedRoute: ActivatedRoute, protected toastService: ToastService, protected dataService: RiskInsightsDataService, - private router: Router, - // protected allActivitiesService: AllActivitiesService, ) { this.searchControl.valueChanges .pipe(debounceTime(200), takeUntilDestroyed()) @@ -78,8 +84,21 @@ export class AllApplicationsComponent implements OnInit { async ngOnInit() { this.dataService.enrichedReportData$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: (report) => { - this.applicationSummary = report?.summaryData ?? createNewSummaryData(); - this.dataSource.data = report?.reportData ?? []; + if (report != null) { + this.applicationSummary.set(report.summaryData); + + // Map the report data to include the iconCipher for each application + const tableDataWithIcon = report.reportData.map((app) => ({ + ...app, + iconCipher: + app.cipherIds.length > 0 + ? this.dataService.getCipherIcon(app.cipherIds[0]) + : undefined, + })); + this.dataSource.data = tableDataWithIcon; + } else { + this.dataSource.data = []; + } }, error: () => { this.dataSource.data = []; @@ -88,15 +107,15 @@ export class AllApplicationsComponent implements OnInit { } isMarkedAsCriticalItem(applicationName: string) { - return this.selectedUrls.has(applicationName); + return this.selectedUrls().has(applicationName); } markAppsAsCritical = async () => { - this.markingAsCritical = true; - const count = this.selectedUrls.size; + this.markingAsCritical.set(true); + const count = this.selectedUrls().size; this.dataService - .saveCriticalApplications(Array.from(this.selectedUrls)) + .saveCriticalApplications(Array.from(this.selectedUrls())) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { @@ -105,8 +124,8 @@ export class AllApplicationsComponent implements OnInit { title: "", message: this.i18nService.t("criticalApplicationsMarkedSuccess", count.toString()), }); - this.selectedUrls.clear(); - this.markingAsCritical = false; + this.selectedUrls.set(new Set<string>()); + this.markingAsCritical.set(false); }, error: () => { this.toastService.showToast({ @@ -125,9 +144,15 @@ export class AllApplicationsComponent implements OnInit { onCheckboxChange = (applicationName: string, event: Event) => { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { - this.selectedUrls.add(applicationName); + this.selectedUrls.update((selectedUrls) => { + selectedUrls.add(applicationName); + return selectedUrls; + }); } else { - this.selectedUrls.delete(applicationName); + this.selectedUrls.update((selectedUrls) => { + selectedUrls.delete(applicationName); + return selectedUrls; + }); } }; } diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts index 1ea745929db..b61190df660 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/critical-applications/critical-applications.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; @@ -8,7 +8,6 @@ import { debounceTime, EMPTY, from, map, switchMap, take } from "rxjs"; import { Security } from "@bitwarden/assets/svg"; import { - ApplicationHealthReportDetailEnriched, CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, @@ -30,12 +29,14 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { RiskInsightsTabType } from "../models/risk-insights.models"; -import { AppTableRowScrollableComponent } from "../shared/app-table-row-scrollable.component"; +import { + ApplicationTableDataSource, + AppTableRowScrollableComponent, +} from "../shared/app-table-row-scrollable.component"; import { AccessIntelligenceSecurityTasksService } from "../shared/security-tasks.service"; -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "dirt-critical-applications", templateUrl: "./critical-applications.component.html", imports: [ @@ -55,7 +56,7 @@ export class CriticalApplicationsComponent implements OnInit { protected organizationId: OrganizationId; noItemsIcon = Security; - protected dataSource = new TableDataSource<ApplicationHealthReportDetailEnriched>(); + protected dataSource = new TableDataSource<ApplicationTableDataSource>(); protected applicationSummary = {} as OrganizationReportSummary; protected selectedIds: Set<number> = new Set<number>(); @@ -79,9 +80,24 @@ export class CriticalApplicationsComponent implements OnInit { async ngOnInit() { this.dataService.criticalReportResults$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe({ next: (criticalReport) => { - this.dataSource.data = criticalReport?.reportData ?? []; - this.applicationSummary = criticalReport?.summaryData ?? createNewSummaryData(); - this.enableRequestPasswordChange = criticalReport?.summaryData?.totalAtRiskMemberCount > 0; + if (criticalReport != null) { + // Map the report data to include the iconCipher for each application + const tableDataWithIcon = criticalReport.reportData.map((app) => ({ + ...app, + iconCipher: + app.cipherIds.length > 0 + ? this.dataService.getCipherIcon(app.cipherIds[0]) + : undefined, + })); + this.dataSource.data = tableDataWithIcon; + + this.applicationSummary = criticalReport.summaryData; + this.enableRequestPasswordChange = criticalReport.summaryData.totalAtRiskMemberCount > 0; + } else { + this.dataSource.data = []; + this.applicationSummary = createNewSummaryData(); + this.enableRequestPasswordChange = false; + } }, error: () => { this.dataSource.data = []; diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html index 79af3869d99..edd90eaf97d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html @@ -46,10 +46,7 @@ [attr.aria-label]="'viewItem' | i18n" > <!-- Passing the first cipher of the application for app-vault-icon cipher input requirement --> - <app-vault-icon - *ngIf="row.cipherIds.length > 0" - [cipher]="row.cipherIds[0]" - ></app-vault-icon> + <app-vault-icon *ngIf="row.iconCipher" [cipher]="row.iconCipher"></app-vault-icon> </td> <td class="tw-cursor-pointer" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts index f2ecff75847..87e58ace898 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts @@ -3,10 +3,15 @@ import { Component, Input } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApplicationHealthReportDetailEnriched } from "@bitwarden/bit-common/dirt/reports/risk-insights"; +import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; +export type ApplicationTableDataSource = ApplicationHealthReportDetailEnriched & { + iconCipher: CipherViewLike | undefined; +}; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -18,7 +23,7 @@ export class AppTableRowScrollableComponent { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @Input() - dataSource!: TableDataSource<ApplicationHealthReportDetailEnriched>; + dataSource!: TableDataSource<ApplicationTableDataSource>; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals @Input() showRowMenuForCriticalApps: boolean = false; From 96ffb567711f8f5cdab41e4d3a6d9bc36f1af971 Mon Sep 17 00:00:00 2001 From: Vicki League <vleague@bitwarden.com> Date: Tue, 18 Nov 2025 11:25:48 -0500 Subject: [PATCH 158/249] [CL-928] Set link component to semibold weight (#17395) --- libs/components/src/link/link.directive.ts | 2 +- libs/components/src/typography/typography.stories.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index df124a6811d..e6de8ac8402 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -25,7 +25,7 @@ const commonStyles = [ "tw-leading-none", "tw-px-0", "tw-py-0.5", - "tw-font-medium", + "tw-font-semibold", "tw-bg-transparent", "tw-border-0", "tw-border-none", diff --git a/libs/components/src/typography/typography.stories.ts b/libs/components/src/typography/typography.stories.ts index bb055be79a0..f9c57ae2637 100644 --- a/libs/components/src/typography/typography.stories.ts +++ b/libs/components/src/typography/typography.stories.ts @@ -27,42 +27,42 @@ const typographyProps: TypographyData[] = [ { id: "h1", typography: "h1", - weight: "Regular", + weight: "Medium", size: 30, lineHeight: "150%", }, { id: "h2", typography: "h2", - weight: "Regular", + weight: "Medium", size: 24, lineHeight: "150%", }, { id: "h3", typography: "h3", - weight: "Regular", + weight: "Medium", size: 20, lineHeight: "150%", }, { id: "h4", typography: "h4", - weight: "Regular", + weight: "Medium", size: 18, lineHeight: "150%", }, { id: "h5", typography: "h5", - weight: "Regular", + weight: "Medium", size: 16, lineHeight: "150%", }, { id: "h6", typography: "h6", - weight: "Regular", + weight: "Medium", size: 14, lineHeight: "150%", }, From 9d15ae80ca4910e60406b3252e2cb8c28bcc6619 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:30:27 -0600 Subject: [PATCH 159/249] [deps] Tools: Update @types/papaparse to v5.5.0 (#17454) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82d05df83d2..5d9d43419f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.16", + "@types/papaparse": "5.5.0", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", @@ -14459,9 +14459,9 @@ } }, "node_modules/@types/papaparse": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.16.tgz", - "integrity": "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.0.tgz", + "integrity": "sha512-GVs5iMQmUr54BAZYYkByv8zPofFxmyxUpISPb2oh8sayR3+1zbxasrOvoKiHJ/nnoq/uULuPsu1Lze1EkagVFg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 53fcb74ce80..3e5a2581e8f 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", - "@types/papaparse": "5.3.16", + "@types/papaparse": "5.5.0", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", From 60977b02771c4393699be8ae3ef59b85db8e12c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:46:55 +0100 Subject: [PATCH 160/249] [deps] Platform: Update electron-log to v5.4.3 (#17455) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5d9d43419f2..9c3c7e2226c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -136,7 +136,7 @@ "css-loader": "7.1.2", "electron": "37.7.0", "electron-builder": "26.0.12", - "electron-log": "5.4.0", + "electron-log": "5.4.3", "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", @@ -20938,9 +20938,9 @@ } }, "node_modules/electron-log": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.0.tgz", - "integrity": "sha512-AXI5OVppskrWxEAmCxuv8ovX+s2Br39CpCAgkGMNHQtjYT3IiVbSQTncEjFVGPgoH35ZygRm/mvUMBDWwhRxgg==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.4.3.tgz", + "integrity": "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 3e5a2581e8f..d97b7755de8 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "css-loader": "7.1.2", "electron": "37.7.0", "electron-builder": "26.0.12", - "electron-log": "5.4.0", + "electron-log": "5.4.3", "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.6.4", From 82a0b3aa70be774cd48e938d00cddfb400256e2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:48:52 +0100 Subject: [PATCH 161/249] [deps] Platform: Update semver to v7.7.3 (#17457) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 26e1183004a..00686959ef0 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -87,7 +87,7 @@ "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "semver": "7.7.2", + "semver": "7.7.3", "tldts": "7.0.1", "zxcvbn": "4.4.2" } diff --git a/package-lock.json b/package-lock.json index 9c3c7e2226c..f673947be92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "qrcode-parser": "2.1.3", "qrious": "4.0.2", "rxjs": "7.8.1", - "semver": "7.7.2", + "semver": "7.7.3", "tabbable": "6.3.0", "tldts": "7.0.1", "ts-node": "10.9.2", @@ -225,7 +225,7 @@ "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", - "semver": "7.7.2", + "semver": "7.7.3", "tldts": "7.0.1", "zxcvbn": "4.4.2" }, @@ -1936,6 +1936,19 @@ "strip-json-comments": "3.1.1" } }, + "node_modules/@angular-eslint/schematics/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@angular-eslint/template-parser": { "version": "19.6.0", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-19.6.0.tgz", @@ -36903,9 +36916,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -39354,19 +39367,6 @@ } } }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-jest/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", diff --git a/package.json b/package.json index d97b7755de8..42162a6189b 100644 --- a/package.json +++ b/package.json @@ -203,7 +203,7 @@ "qrcode-parser": "2.1.3", "qrious": "4.0.2", "rxjs": "7.8.1", - "semver": "7.7.2", + "semver": "7.7.3", "tabbable": "6.3.0", "tldts": "7.0.1", "ts-node": "10.9.2", From 3b84da60cafa79820d5ff89cf7c5336e9aa3775b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:01:53 +0000 Subject: [PATCH 162/249] [deps] Platform: Update @types/chrome to v0.1.27 (#17453) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f673947be92..b7b625fdde2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,7 @@ "@storybook/theming": "8.6.12", "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", - "@types/chrome": "0.1.12", + "@types/chrome": "0.1.27", "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", @@ -13957,9 +13957,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.12.tgz", - "integrity": "sha512-jEkxs9GPQHx7g49WjkA8QDNcqODbMGDuBbWQOtjiS/Wf9AiEcDmQMIAgJvC/Xi36WoCVNx584g0Dd9ThJQCAiw==", + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.27.tgz", + "integrity": "sha512-pkkCb0Ft8X+Igi751POzT+YqchSxUCtB6s4Gs6ttgSj8qzJga/qlJMgSW1mKxuQTW4i0sTqQbqVtzXDS5AU+4A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 42162a6189b..52a7d1c60a2 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@storybook/theming": "8.6.12", "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", - "@types/chrome": "0.1.12", + "@types/chrome": "0.1.27", "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", From b1acff7f5c09a82b919a40544b413824242f7fa6 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Tue, 18 Nov 2025 12:22:13 -0500 Subject: [PATCH 163/249] Pm 27900 add additional hardening in extension frame validation (#17265) * PM-27900 harden iframe, origin route tightening and test updates * reduce comments to make more legible * Removes referrer check in favor of PM-27822 #17313 bitwarden/clients@4206447cfece36766ed413791821ac6156e12ba8 * nake token optional since it is later set * whitelist -> allowlist * improve notes on unsafe * improve content handler notes * order allowlist * improve jsdoc on ismessagefromextension method * cover additional test cases * rename verifytoken and document more clear, update referrer --------- Co-authored-by: Miles Blackwood <mrobinson@bitwarden.com> --- .../content/content-message-handler.ts | 19 ++- .../autofill-inline-menu-container.ts | 1 + .../autofill-inline-menu-container.spec.ts | 71 +++++++- .../autofill-inline-menu-container.ts | 156 +++++++++++++++--- .../autofill-inline-menu-page-element.ts | 20 ++- 5 files changed, 233 insertions(+), 34 deletions(-) diff --git a/apps/browser/src/autofill/content/content-message-handler.ts b/apps/browser/src/autofill/content/content-message-handler.ts index c57b2d959f3..63afc215923 100644 --- a/apps/browser/src/autofill/content/content-message-handler.ts +++ b/apps/browser/src/autofill/content/content-message-handler.ts @@ -86,17 +86,30 @@ function handleOpenBrowserExtensionToUrlMessage({ url }: { url?: ExtensionPageUr } /** - * Handles the window message event. + * Handles window message events, validating source and extracting referrer for security. * * @param event - The window message event */ function handleWindowMessageEvent(event: MessageEvent) { - const { source, data } = event; + const { source, data, origin } = event; if (source !== window || !data?.command) { return; } - const referrer = source.location.hostname; + // Extract hostname from event.origin for secure referrer validation in background script + let referrer: string; + // Sandboxed iframe or opaque origin support + if (origin === "null") { + referrer = "null"; + } else { + try { + const originUrl = new URL(origin); + referrer = originUrl.hostname; + } catch { + return; + } + } + const handler = windowMessageHandlers[data.command]; if (handler) { handler({ data, referrer }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts index a147e0ba165..af60d1de77d 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts @@ -5,6 +5,7 @@ import { InlineMenuCipherData } from "../../../background/abstractions/overlay.b export type AutofillInlineMenuContainerMessage = { command: string; portKey: string; + token?: string; }; export type InitAutofillInlineMenuElementMessage = AutofillInlineMenuContainerMessage & { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts index f7a5727e47f..d7a61bec61f 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts @@ -6,11 +6,13 @@ import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container"; describe("AutofillInlineMenuContainer", () => { const portKey = "testPortKey"; - const iframeUrl = "https://example.com"; + const extensionOrigin = "chrome-extension://test-extension-id"; + const iframeUrl = `${extensionOrigin}/overlay/menu-list.html`; const pageTitle = "Example"; let autofillInlineMenuContainer: AutofillInlineMenuContainer; beforeEach(() => { + jest.spyOn(chrome.runtime, "getURL").mockReturnValue(`${extensionOrigin}/`); autofillInlineMenuContainer = new AutofillInlineMenuContainer(); }); @@ -28,7 +30,7 @@ describe("AutofillInlineMenuContainer", () => { portName: AutofillOverlayPort.List, }; - postWindowMessage(message); + postWindowMessage(message, extensionOrigin); expect(autofillInlineMenuContainer["defaultIframeAttributes"].src).toBe(message.iframeUrl); expect(autofillInlineMenuContainer["defaultIframeAttributes"].title).toBe(message.pageTitle); @@ -44,15 +46,48 @@ describe("AutofillInlineMenuContainer", () => { portName: AutofillOverlayPort.Button, }; - postWindowMessage(message); + postWindowMessage(message, extensionOrigin); jest.spyOn(autofillInlineMenuContainer["inlineMenuPageIframe"].contentWindow, "postMessage"); autofillInlineMenuContainer["inlineMenuPageIframe"].dispatchEvent(new Event("load")); expect(chrome.runtime.connect).toHaveBeenCalledWith({ name: message.portName }); + const expectedMessage = expect.objectContaining({ + ...message, + token: expect.any(String), + }); expect( autofillInlineMenuContainer["inlineMenuPageIframe"].contentWindow.postMessage, - ).toHaveBeenCalledWith(message, "*"); + ).toHaveBeenCalledWith(expectedMessage, "*"); + }); + + it("ignores initialization when URLs are not from extension origin", () => { + const invalidIframeUrlMessage = { + command: "initAutofillInlineMenuList", + iframeUrl: "https://malicious.com/overlay/menu-list.html", + pageTitle, + portKey, + portName: AutofillOverlayPort.List, + }; + + postWindowMessage(invalidIframeUrlMessage, extensionOrigin); + expect(autofillInlineMenuContainer["inlineMenuPageIframe"]).toBeUndefined(); + expect(autofillInlineMenuContainer["isInitialized"]).toBe(false); + + autofillInlineMenuContainer = new AutofillInlineMenuContainer(); + + const invalidStyleSheetUrlMessage = { + command: "initAutofillInlineMenuList", + iframeUrl, + pageTitle, + portKey, + portName: AutofillOverlayPort.List, + styleSheetUrl: "https://malicious.com/styles.css", + }; + + postWindowMessage(invalidStyleSheetUrlMessage, extensionOrigin); + expect(autofillInlineMenuContainer["inlineMenuPageIframe"]).toBeUndefined(); + expect(autofillInlineMenuContainer["isInitialized"]).toBe(false); }); }); @@ -69,7 +104,7 @@ describe("AutofillInlineMenuContainer", () => { portName: AutofillOverlayPort.Button, }; - postWindowMessage(message); + postWindowMessage(message, extensionOrigin); iframe = autofillInlineMenuContainer["inlineMenuPageIframe"]; jest.spyOn(iframe.contentWindow, "postMessage"); @@ -112,7 +147,8 @@ describe("AutofillInlineMenuContainer", () => { }); it("posts a message to the background from the inline menu iframe", () => { - const message = { command: "checkInlineMenuButtonFocused", portKey }; + const token = autofillInlineMenuContainer["token"]; + const message = { command: "checkInlineMenuButtonFocused", portKey, token }; postWindowMessage(message, "null", iframe.contentWindow as any); @@ -124,7 +160,28 @@ describe("AutofillInlineMenuContainer", () => { postWindowMessage(message); - expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(message, "*"); + const expectedMessage = expect.objectContaining({ + ...message, + token: expect.any(String), + }); + expect(iframe.contentWindow.postMessage).toHaveBeenCalledWith(expectedMessage, "*"); + }); + + it("ignores messages from iframe with invalid token", () => { + const message = { command: "checkInlineMenuButtonFocused", portKey, token: "invalid-token" }; + + postWindowMessage(message, "null", iframe.contentWindow as any); + + expect(port.postMessage).not.toHaveBeenCalled(); + }); + + it("ignores messages from iframe with commands not in the allowlist", () => { + const token = autofillInlineMenuContainer["token"]; + const message = { command: "maliciousCommand", portKey, token }; + + postWindowMessage(message, "null", iframe.contentWindow as any); + + expect(port.postMessage).not.toHaveBeenCalled(); }); }); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts index 6d85982a1ac..ad0b11f0bc6 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts @@ -1,6 +1,6 @@ import { EVENTS } from "@bitwarden/common/autofill/constants"; -import { setElementStyles } from "../../../../utils"; +import { generateRandomChars, setElementStyles } from "../../../../utils"; import { InitAutofillInlineMenuElementMessage, AutofillInlineMenuContainerWindowMessageHandlers, @@ -8,14 +8,37 @@ import { AutofillInlineMenuContainerPortMessage, } from "../../abstractions/autofill-inline-menu-container"; +/** + * Allowlist of commands that can be sent to the background script. + */ +const ALLOWED_BG_COMMANDS = new Set<string>([ + "addNewVaultItem", + "autofillInlineMenuBlurred", + "autofillInlineMenuButtonClicked", + "checkAutofillInlineMenuButtonFocused", + "checkInlineMenuButtonFocused", + "fillAutofillInlineMenuCipher", + "fillGeneratedPassword", + "redirectAutofillInlineMenuFocusOut", + "refreshGeneratedPassword", + "refreshOverlayCiphers", + "triggerDelayedAutofillInlineMenuClosure", + "updateAutofillInlineMenuColorScheme", + "updateAutofillInlineMenuListHeight", + "unlockVault", + "viewSelectedCipher", +]); + export class AutofillInlineMenuContainer { private readonly setElementStyles = setElementStyles; - private readonly extensionOriginsSet: Set<string>; private port: chrome.runtime.Port | null = null; /** Non-null asserted. */ private portName!: string; /** Non-null asserted. */ private inlineMenuPageIframe!: HTMLIFrameElement; + private token: string; + private isInitialized: boolean = false; + private readonly extensionOrigin: string; private readonly iframeStyles: Partial<CSSStyleDeclaration> = { all: "initial", position: "fixed", @@ -49,11 +72,8 @@ export class AutofillInlineMenuContainer { }; constructor() { - this.extensionOriginsSet = new Set([ - chrome.runtime.getURL("").slice(0, -1).toLowerCase(), // Remove the trailing slash and normalize the extension url to lowercase - "null", - ]); - + this.token = generateRandomChars(32); + this.extensionOrigin = chrome.runtime.getURL("").slice(0, -1); globalThis.addEventListener("message", this.handleWindowMessage); } @@ -63,9 +83,22 @@ export class AutofillInlineMenuContainer { * @param message - The message containing the iframe url and page title. */ private handleInitInlineMenuIframe(message: InitAutofillInlineMenuElementMessage) { + if (this.isInitialized) { + return; + } + + if (!this.isExtensionUrl(message.iframeUrl)) { + return; + } + + if (message.styleSheetUrl && !this.isExtensionUrl(message.styleSheetUrl)) { + return; + } + this.defaultIframeAttributes.src = message.iframeUrl; this.defaultIframeAttributes.title = message.pageTitle; this.portName = message.portName; + this.isInitialized = true; this.inlineMenuPageIframe = globalThis.document.createElement("iframe"); this.setElementStyles(this.inlineMenuPageIframe, this.iframeStyles, true); @@ -81,6 +114,26 @@ export class AutofillInlineMenuContainer { globalThis.document.body.appendChild(this.inlineMenuPageIframe); } + /** + * validates that a URL is from the extension origin. + * prevents loading arbitrary URLs in the iframe. + * + * @param url - The URL to validate. + */ + private isExtensionUrl(url: string): boolean { + if (!url) { + return false; + } + try { + const urlObj = new URL(url); + return ( + urlObj.origin === this.extensionOrigin || urlObj.href.startsWith(this.extensionOrigin + "/") + ); + } catch { + return false; + } + } + /** * Sets up the port message listener for the inline menu page. * @@ -88,7 +141,8 @@ export class AutofillInlineMenuContainer { */ private setupPortMessageListener = (message: InitAutofillInlineMenuElementMessage) => { this.port = chrome.runtime.connect({ name: this.portName }); - this.postMessageToInlineMenuPage(message); + const initMessage = { ...message, token: this.token }; + this.postMessageToInlineMenuPageUnsafe(initMessage); }; /** @@ -97,6 +151,22 @@ export class AutofillInlineMenuContainer { * @param message - The message to post. */ private postMessageToInlineMenuPage(message: AutofillInlineMenuContainerWindowMessage) { + if (this.inlineMenuPageIframe?.contentWindow) { + const messageWithToken = { ...message, token: this.token }; + this.postMessageToInlineMenuPageUnsafe(messageWithToken); + } + } + + /** + * Posts a message to the inline menu page iframe without token validation. + * + * UNSAFE: Bypasses token authentication and sends raw messages. Only use internally + * when sending trusted messages (e.g., initialization) or when token validation + * would create circular dependencies. External callers should use postMessageToInlineMenuPage(). + * + * @param message - The message to post. + */ + private postMessageToInlineMenuPageUnsafe(message: Record<string, unknown>) { if (this.inlineMenuPageIframe?.contentWindow) { this.inlineMenuPageIframe.contentWindow.postMessage(message, "*"); } @@ -108,9 +178,15 @@ export class AutofillInlineMenuContainer { * @param message - The message to post. */ private postMessageToBackground(message: AutofillInlineMenuContainerPortMessage) { - if (this.port) { - this.port.postMessage(message); + if (!this.port) { + return; } + + if (message.command && !ALLOWED_BG_COMMANDS.has(message.command)) { + return; + } + + this.port.postMessage(message); } /** @@ -124,23 +200,33 @@ export class AutofillInlineMenuContainer { return; } - if ( - this.windowMessageHandlers[ - message.command as keyof AutofillInlineMenuContainerWindowMessageHandlers - ] - ) { - this.windowMessageHandlers[ - message.command as keyof AutofillInlineMenuContainerWindowMessageHandlers - ](message); + if (this.windowMessageHandlers[message.command]) { + // only accept init messages from extension origin or parent window + if ( + (message.command === "initAutofillInlineMenuButton" || + message.command === "initAutofillInlineMenuList") && + !this.isMessageFromExtensionOrigin(event) && + !this.isMessageFromParentWindow(event) + ) { + return; + } + this.windowMessageHandlers[message.command](message); return; } if (this.isMessageFromParentWindow(event)) { + // messages from parent window are trusted and forwarded to iframe this.postMessageToInlineMenuPage(message); return; } - this.postMessageToBackground(message); + // messages from iframe to background require object identity verification with a contentWindow check and token auth + if (this.isMessageFromInlineMenuPageIframe(event)) { + if (this.isValidSessionToken(message)) { + this.postMessageToBackground(message); + } + return; + } }; /** @@ -184,10 +270,34 @@ export class AutofillInlineMenuContainer { if (!this.inlineMenuPageIframe) { return false; } + // only trust the specific iframe we created + return this.inlineMenuPageIframe.contentWindow === event.source; + } - return ( - this.inlineMenuPageIframe.contentWindow === event.source && - this.extensionOriginsSet.has(event.origin.toLowerCase()) - ); + /** + * Validates that the message contains a valid session token. + * The session token is generated when the container is created and is refreshed + * every time the inline menu container is recreated. + * + */ + private isValidSessionToken(message: { token?: string }): boolean { + return message.token === this.token; + } + + /** + * Validates that a message event originates from the extension. + * + * @param event - The message event to validate. + * @returns True if the message is from the extension origin. + */ + private isMessageFromExtensionOrigin(event: MessageEvent): boolean { + try { + if (event.origin === "null") { + return false; + } + return event.origin === this.extensionOrigin; + } catch { + return false; + } } } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts index 89f44a6a80d..ea77e3e434d 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts @@ -16,6 +16,7 @@ export class AutofillInlineMenuPageElement extends HTMLElement { private portKey!: string; /** Non-null asserted. */ protected windowMessageHandlers!: AutofillInlineMenuPageElementWindowMessageHandlers; + private token?: string; constructor() { super(); @@ -37,8 +38,12 @@ export class AutofillInlineMenuPageElement extends HTMLElement { styleSheetUrl: string, translations: Record<string, string>, portKey: string, + token?: string, ): Promise<HTMLLinkElement> { this.portKey = portKey; + if (token) { + this.token = token; + } this.translations = translations; globalThis.document.documentElement.setAttribute("lang", this.getTranslation("locale")); @@ -58,7 +63,11 @@ export class AutofillInlineMenuPageElement extends HTMLElement { * @param message - The message to post */ protected postMessageToParent(message: AutofillInlineMenuPageElementWindowMessage) { - globalThis.parent.postMessage({ portKey: this.portKey, ...message }, "*"); + const messageWithAuth: Record<string, unknown> = { portKey: this.portKey, ...message }; + if (this.token) { + messageWithAuth.token = this.token; + } + globalThis.parent.postMessage(messageWithAuth, "*"); } /** @@ -105,6 +114,15 @@ export class AutofillInlineMenuPageElement extends HTMLElement { } const message = event?.data; + + if ( + message?.token && + (message?.command === "initAutofillInlineMenuButton" || + message?.command === "initAutofillInlineMenuList") + ) { + this.token = message.token; + } + const handler = this.windowMessageHandlers[message?.command]; if (!handler) { return; From fa563641b20bb0f66ac039d140510e1b6f2850b5 Mon Sep 17 00:00:00 2001 From: Vince Grassia <593223+vgrassia@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:23:39 -0500 Subject: [PATCH 164/249] BRE-1355 - Rename Bitwarden Unified to Bitwarden Lite (#17456) --- .github/workflows/publish-web.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 6bf2b282b38..4f41898a9b2 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -157,11 +157,10 @@ jobs: - name: Log out of Docker run: docker logout - self-host-unified-build: - name: Trigger self-host unified build + bitwarden-lite-build: + name: Trigger Bitwarden Lite build runs-on: ubuntu-22.04 - needs: - - setup + needs: setup permissions: id-token: write steps: @@ -182,7 +181,7 @@ jobs: - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - - name: Trigger self-host build + - name: Trigger Bitwarden Lite build uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }} @@ -190,7 +189,7 @@ jobs: await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', - workflow_id: 'build-unified.yml', + workflow_id: 'build-bitwarden-lite.yml', ref: 'main', inputs: { use_latest_core_version: true From 0d14060e9d2cd317aee2a4b07a93f16e742352ec Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:33:12 +0100 Subject: [PATCH 165/249] Autosync the updated translations (#17460) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/he/messages.json | 20 ++++++++++---------- apps/desktop/src/locales/sk/messages.json | 18 +++++++++--------- apps/desktop/src/locales/zh_CN/messages.json | 4 ++-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json index 87fac938a34..868cd9ccbc5 100644 --- a/apps/desktop/src/locales/he/messages.json +++ b/apps/desktop/src/locales/he/messages.json @@ -4195,33 +4195,33 @@ "message": "מספר כרטיס" }, "upgradeNow": { - "message": "Upgrade now" + "message": "שדרג עכשיו" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "מאמת מובנה" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "אחסון קבצים מאובטח" }, "emergencyAccess": { - "message": "Emergency access" + "message": "גישת חירום" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "ניטור פרצות" }, "andMoreFeatures": { - "message": "And more!" + "message": "ועוד!" }, "planDescPremium": { - "message": "Complete online security" + "message": "השלם אבטחה מקוונת" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "שדרג לפרימיום" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "פעולת פסק זמן" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "פסק זמן להפעלה" } } diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json index e5763d78b9c..0b14b961bbb 100644 --- a/apps/desktop/src/locales/sk/messages.json +++ b/apps/desktop/src/locales/sk/messages.json @@ -1692,7 +1692,7 @@ "description": "Default URI match detection for auto-fill." }, "toggleOptions": { - "message": "Voľby prepínača" + "message": "Zobraziť/skryť možnosti" }, "organization": { "message": "Organizácia", @@ -2425,7 +2425,7 @@ "message": "Hlavné heslo bolo úspešne nastavené" }, "updatedMasterPassword": { - "message": "Hlavné heslo aktualizované" + "message": "Hlavné heslo bolo aktualizované" }, "updateMasterPassword": { "message": "Aktualizovať hlavné heslo" @@ -2476,7 +2476,7 @@ "message": "Použiť PIN kód" }, "useBiometrics": { - "message": "Použiť biometrické údaje" + "message": "Použiť biometriu" }, "enterVerificationCodeSentToEmail": { "message": "Zadajte overovací kód, ktorý vám bol zaslaný na e-mail." @@ -2574,7 +2574,7 @@ "message": "Táto organizácia má podnikovú politiku, ktorá vás automaticky zaregistruje na obnovenie hesla. Registrácia umožní správcom organizácie zmeniť vaše hlavné heslo." }, "vaultExportDisabled": { - "message": "Export trezoru je zakázaný" + "message": "Export trezoru bol odstránený" }, "personalVaultExportPolicyInEffect": { "message": "Jedna alebo viacero zásad organizácie vám bráni exportovať váš osobný trezor." @@ -2619,7 +2619,7 @@ "message": "Predvoľby" }, "appPreferences": { - "message": "Nastavenia aplikácie (Všetky účty)" + "message": "Nastavenia aplikácie (všetky účty)" }, "accountSwitcherLimitReached": { "message": "Dosiahnutý limit počtu účtov. Odhláste sa z účtu aby ste mohli pridať ďalší." @@ -2790,7 +2790,7 @@ "message": "Použiť možnosti subadresovania svojho poskytovateľa e-mailu." }, "catchallEmail": { - "message": "Catch-all Email" + "message": "E-mail Catch-all" }, "catchallEmailDesc": { "message": "Použiť doručenú poštu typu catch-all nastavenú na doméne." @@ -2843,7 +2843,7 @@ "description": "Guidance provided for email forwarding services that support multiple email domains." }, "forwarderError": { - "message": "$SERVICENAME$ chyba: $ERRORMESSAGE$", + "message": "Chyba $SERVICENAME$: $ERRORMESSAGE$", "description": "Reports an error returned by a forwarding service to the user.", "placeholders": { "servicename": { @@ -2959,7 +2959,7 @@ } }, "forwarderUnknownForwarder": { - "message": "Nepodporovaná služba: '$SERVICENAME$'.", + "message": "Neznáme presmerovanie: '$SERVICENAME$'.", "description": "Displayed when the forwarding service is not supported.", "placeholders": { "servicename": { @@ -2976,7 +2976,7 @@ "message": "Prístupový token API" }, "apiKey": { - "message": "API kľúč" + "message": "Kľúč API" }, "premiumSubcriptionRequired": { "message": "Vyžaduje sa predplatné Prémium" diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json index 353fc036f63..b5e68b83bde 100644 --- a/apps/desktop/src/locales/zh_CN/messages.json +++ b/apps/desktop/src/locales/zh_CN/messages.json @@ -1503,7 +1503,7 @@ "message": "优先客户支持。" }, "premiumSignUpFuture": { - "message": "所有未来的高级功能。即将推出!" + "message": "未来的更多高级版功能。敬请期待!" }, "premiumPurchase": { "message": "购买高级版" @@ -2029,7 +2029,7 @@ "message": "Bitwarden 可以存储并填充两步验证码。选择相机图标来拍摄此网站的验证器二维码,或将密钥复制并粘贴到此字段。" }, "premium": { - "message": "高级会员", + "message": "高级版", "description": "Premium membership" }, "freeOrgsCannotUseAttachments": { From bbb42d9b17a3fd4666b2b887993687b59c8a90da Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:36:24 +0100 Subject: [PATCH 166/249] Autosync the updated translations (#17461) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 9 +++++ apps/browser/src/_locales/az/messages.json | 11 +++++- apps/browser/src/_locales/be/messages.json | 9 +++++ apps/browser/src/_locales/bg/messages.json | 11 +++++- apps/browser/src/_locales/bn/messages.json | 9 +++++ apps/browser/src/_locales/bs/messages.json | 9 +++++ apps/browser/src/_locales/ca/messages.json | 9 +++++ apps/browser/src/_locales/cs/messages.json | 9 +++++ apps/browser/src/_locales/cy/messages.json | 9 +++++ apps/browser/src/_locales/da/messages.json | 9 +++++ apps/browser/src/_locales/de/messages.json | 11 +++++- apps/browser/src/_locales/el/messages.json | 9 +++++ apps/browser/src/_locales/en_GB/messages.json | 9 +++++ apps/browser/src/_locales/en_IN/messages.json | 9 +++++ apps/browser/src/_locales/es/messages.json | 9 +++++ apps/browser/src/_locales/et/messages.json | 9 +++++ apps/browser/src/_locales/eu/messages.json | 9 +++++ apps/browser/src/_locales/fa/messages.json | 9 +++++ apps/browser/src/_locales/fi/messages.json | 9 +++++ apps/browser/src/_locales/fil/messages.json | 9 +++++ apps/browser/src/_locales/fr/messages.json | 9 +++++ apps/browser/src/_locales/gl/messages.json | 9 +++++ apps/browser/src/_locales/he/messages.json | 39 ++++++++++++------- apps/browser/src/_locales/hi/messages.json | 9 +++++ apps/browser/src/_locales/hr/messages.json | 9 +++++ apps/browser/src/_locales/hu/messages.json | 9 +++++ apps/browser/src/_locales/id/messages.json | 9 +++++ apps/browser/src/_locales/it/messages.json | 9 +++++ apps/browser/src/_locales/ja/messages.json | 9 +++++ apps/browser/src/_locales/ka/messages.json | 9 +++++ apps/browser/src/_locales/km/messages.json | 9 +++++ apps/browser/src/_locales/kn/messages.json | 9 +++++ apps/browser/src/_locales/ko/messages.json | 9 +++++ apps/browser/src/_locales/lt/messages.json | 9 +++++ apps/browser/src/_locales/lv/messages.json | 9 +++++ apps/browser/src/_locales/ml/messages.json | 9 +++++ apps/browser/src/_locales/mr/messages.json | 9 +++++ apps/browser/src/_locales/my/messages.json | 9 +++++ apps/browser/src/_locales/nb/messages.json | 9 +++++ apps/browser/src/_locales/ne/messages.json | 9 +++++ apps/browser/src/_locales/nl/messages.json | 9 +++++ apps/browser/src/_locales/nn/messages.json | 9 +++++ apps/browser/src/_locales/or/messages.json | 9 +++++ apps/browser/src/_locales/pl/messages.json | 9 +++++ apps/browser/src/_locales/pt_BR/messages.json | 9 +++++ apps/browser/src/_locales/pt_PT/messages.json | 9 +++++ apps/browser/src/_locales/ro/messages.json | 9 +++++ apps/browser/src/_locales/ru/messages.json | 9 +++++ apps/browser/src/_locales/si/messages.json | 9 +++++ apps/browser/src/_locales/sk/messages.json | 11 +++++- apps/browser/src/_locales/sl/messages.json | 9 +++++ apps/browser/src/_locales/sr/messages.json | 9 +++++ apps/browser/src/_locales/sv/messages.json | 11 +++++- apps/browser/src/_locales/ta/messages.json | 9 +++++ apps/browser/src/_locales/te/messages.json | 9 +++++ apps/browser/src/_locales/th/messages.json | 9 +++++ apps/browser/src/_locales/tr/messages.json | 11 +++++- apps/browser/src/_locales/uk/messages.json | 9 +++++ apps/browser/src/_locales/vi/messages.json | 9 +++++ apps/browser/src/_locales/zh_CN/messages.json | 15 +++++-- apps/browser/src/_locales/zh_TW/messages.json | 11 +++++- 61 files changed, 574 insertions(+), 25 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 79d54193b59..20eb31a5453 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index b67d5ace0d4..d8d8589f47e 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -595,7 +595,7 @@ "message": "Hamısına bax" }, "viewLess": { - "message": "View less" + "message": "Daha azına bax" }, "viewLogin": { "message": "Girişə bax" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "\"Premium\"a yüksəlt" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Seyf yüklənir" }, diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 450fb6e3df5..47bafb7efe3 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 0a71e453c21..98da9bf033e 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -595,7 +595,7 @@ "message": "Показване на всички" }, "viewLess": { - "message": "View less" + "message": "Преглед на по-малко" }, "viewLogin": { "message": "Преглед на елемента за вписване" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Надградете до Платения план" }, + "upgradeCompleteSecurity": { + "message": "Надградете, за да се възползвате от пълна защита" + }, + "premiumGivesMoreTools": { + "message": "Платеният план предоставя повече инструменти за защита, ефективна работа и контрол." + }, + "explorePremium": { + "message": "Разгледайте платения план" + }, "loadingVault": { "message": "Зареждане на трезора" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index f43e3fdad29..794e380d012 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 4fbcccd9aae..611cb4f5f55 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 15a309fa8fd..01611cc0764 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index f9f572d87d8..5c0c8fbf524 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Aktualizovat na Premium" }, + "upgradeCompleteSecurity": { + "message": "Aktualizujte pro úplné zabezpečení" + }, + "premiumGivesMoreTools": { + "message": "Verze Premium Vám poskytne více nástrojů k zabezpečení, efektivní práci a udržení kontroly." + }, + "explorePremium": { + "message": "Objevit Premium" + }, "loadingVault": { "message": "Načítání trezoru" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 33c68b338a0..3bd81cc8039 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index a5999945692..9a12f808f22 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index b72cc2decb4..517dc638aff 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -595,7 +595,7 @@ "message": "Alles anzeigen" }, "viewLess": { - "message": "View less" + "message": "Weniger anzeigen" }, "viewLogin": { "message": "Zugangsdaten anzeigen" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade auf Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade für umfassende Sicherheit" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Tresor wird geladen" }, diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index e0de7e5e9e0..ae685fff651 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index a9c57e157e6..5079e1e6689 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index cd8c91f8437..2a34400ea58 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 470cf2ab35a..8100fa80435 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 9220d61e466..0f69561df57 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index c360bed28e0..c60ce5e4da7 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 774a02f50d3..5a03f8427fa 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index 8766632a91e..db4649c33a7 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 6c7154a1ba5..6239e9d4f97 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 0e701750c5b..1cc04809a1b 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index c61325ef8de..090bc6b1493 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 81ac1e176f0..2934d345600 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -595,7 +595,7 @@ "message": "הצג הכל" }, "viewLess": { - "message": "View less" + "message": "הצג פחות" }, "viewLogin": { "message": "הצג כניסה" @@ -800,10 +800,10 @@ "message": "בנעילת המערכת" }, "onIdle": { - "message": "On system idle" + "message": "כשהמערכת מזהה חוסר פעילות" }, "onSleep": { - "message": "On system sleep" + "message": "כשהמערכת נכנסת למצב שינה" }, "onRestart": { "message": "בהפעלת הדפדפן מחדש" @@ -4984,7 +4984,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "ברירת מחדל ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5786,34 +5786,43 @@ "message": "עבודה נהדרת באבטחת הכניסות בסיכון שלך!" }, "upgradeNow": { - "message": "Upgrade now" + "message": "שדרג עכשיו" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "מאמת מובנה" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "אחסון קבצים מאובטח" }, "emergencyAccess": { - "message": "Emergency access" + "message": "גישת חירום" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "ניטור פרצות" }, "andMoreFeatures": { - "message": "And more!" + "message": "ועוד!" }, "planDescPremium": { - "message": "Complete online security" + "message": "השלם אבטחה מקוונת" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "שדרג לפרימיום" + }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "טוען כספת" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "הכספת נטענה" }, "settingDisabledByPolicy": { "message": "הגדרה זו מושבתת על ידי מדיניות של הארגון שלך.", @@ -5826,6 +5835,6 @@ "message": "מספר כרטיס" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "פעולת פסק זמן" } } diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index ff24818f821..75011ebf8e5 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index 052fae33683..fb89e8940e3 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": " Nadogradi na Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Učitavanje trezora" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index fb94c4f4665..f9a2f9ff009 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Áttérés Prémium csomagra" }, + "upgradeCompleteSecurity": { + "message": "Áttérés a teljes biztonságért" + }, + "premiumGivesMoreTools": { + "message": "A Premium több eszközt ad a biztonság megőrzéséhez, a hatékony munkavégzéshez és az irányítás megőrzéséhez." + }, + "explorePremium": { + "message": "Premium felfedezése" + }, "loadingVault": { "message": "Széf betöltése" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index 1cb79804923..a88e3cc2d37 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index dc69cb13cb3..f0e63d99ecf 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 8b3b0e2cc6d..98ac152a08f 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "プレミアムにアップグレード" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index 5c7a8da23a7..e6e8becd50a 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index 3e929bc6533..c83d63762d6 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 5a21928c233..50f49833d50 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index ac598394a8c..21cd9ca401c 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 70f46e0f068..088d671a2b5 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Uzlabot uz Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Ielādē glabātavu" }, diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index d139531315b..d76f579fac4 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index 438cc750557..acbbc97ceac 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 4ae8a01a12f..3933f66c541 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index b3463c9f1b3..a4d38f6c9ba 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Opwaarderen naar Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade voor volledige beveiliging" + }, + "premiumGivesMoreTools": { + "message": "Premium geeft je meer tools om veilig te blijven, efficiënt te werken en in controle te blijven." + }, + "explorePremium": { + "message": "Premium verkennen" + }, "loadingVault": { "message": "Kluis laden" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 77b6cc436d7..0847126f33e 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index c3d96145944..dbd7229db2a 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Faça upgrade para o Premium" }, + "upgradeCompleteSecurity": { + "message": "Faça upgrade para segurança completa" + }, + "premiumGivesMoreTools": { + "message": "O Premium te oferece mais ferramentas para se permanecer seguro, trabalhar eficientemente, e manter o controle." + }, + "explorePremium": { + "message": "Explorar o Premium" + }, "loadingVault": { "message": "Carregando cofre" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 10fbc3db004..55065838ec8 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Atualizar para o Premium" }, + "upgradeCompleteSecurity": { + "message": "Atualize para obter segurança total" + }, + "premiumGivesMoreTools": { + "message": "O Premium oferece mais ferramentas para manter a segurança, trabalhar com eficiência e manter o controlo." + }, + "explorePremium": { + "message": "Explorar o Premium" + }, "loadingVault": { "message": "A carregar o cofre" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 5fe7c61f9cc..84a1937bf14 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 349e68c5194..22e9052fe43 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Обновить до Премиум" }, + "upgradeCompleteSecurity": { + "message": "Перейти для полной защищенности" + }, + "premiumGivesMoreTools": { + "message": "Премиум предоставит вам больше инструментов для обеспечения безопасности, эффективной работы и контроля над ситуацией." + }, + "explorePremium": { + "message": "Познакомиться с Премиум" + }, "loadingVault": { "message": "Загрузка хранилища" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index 9b36684dc5a..f4c00449a2b 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index a269756a414..b8b820def35 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2195,7 +2195,7 @@ "description": "Default URI match detection for autofill." }, "toggleOptions": { - "message": "Voľby prepínača" + "message": "Zobraziť/skryť možnosti" }, "toggleCurrentUris": { "message": "Prepnúť zobrazenie aktuálnej URI", @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgradovať na Prémium" }, + "upgradeCompleteSecurity": { + "message": "Upgradovať pre úplné zabezpečenie" + }, + "premiumGivesMoreTools": { + "message": "Predplatné Prémium vám poskytuje viac nástrojov na zabezpečenie, efektívnu prácu a kontrolu." + }, + "explorePremium": { + "message": "Preskúmať Prémium" + }, "loadingVault": { "message": "Načítava sa trezor" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 3cbd9a11342..70d9c5f70c2 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index d13939f8656..97fcb37fb58 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Надоградите на Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index 9f84e9d714c..ea7a4d80359 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -595,7 +595,7 @@ "message": "Visa alla" }, "viewLess": { - "message": "View less" + "message": "Visa mindre" }, "viewLogin": { "message": "Visa inloggning" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Uppgradera till Premium" }, + "upgradeCompleteSecurity": { + "message": "Uppgradera för fullständig säkerhet" + }, + "premiumGivesMoreTools": { + "message": "Premium ger dig fler verktyg för att hålla dig säker, arbeta effektivt och ha kontroll." + }, + "explorePremium": { + "message": "Utforska Premium" + }, "loadingVault": { "message": "Läser in valv" }, diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index cbefd26424c..a95da4d2059 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index 13e74f8d807..cb1a5c089fe 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 594bc6d7a94..9096067ce3b 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 7f234b8750a..4c7ca6937a4 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -595,7 +595,7 @@ "message": "Tümünü göster" }, "viewLess": { - "message": "View less" + "message": "Daha az göster" }, "viewLogin": { "message": "Hesabı göster" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Premium'a yükselt" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Kasa yükleniyor" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index a17033ee6e8..7f8b0f8b13b 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Покращити до Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 2fdba62adeb..414b4cc2cac 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "Nâng cấp lên gói Cao cấp" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "Loading vault" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 52d8a03b769..548bdd9e178 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -595,7 +595,7 @@ "message": "查看全部" }, "viewLess": { - "message": "View less" + "message": "查看更少" }, "viewLogin": { "message": "查看登录" @@ -1485,7 +1485,7 @@ "message": "优先客户支持。" }, "ppremiumSignUpFuture": { - "message": "未来的更多高级功能。敬请期待!" + "message": "未来的更多高级版功能。敬请期待!" }, "premiumPurchase": { "message": "购买高级版" @@ -4897,7 +4897,7 @@ "message": "确定要永久删除此附件吗?" }, "premium": { - "message": "高级会员" + "message": "高级版" }, "freeOrgsCannotUseAttachments": { "message": "免费组织无法使用附件" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "升级为高级版" }, + "upgradeCompleteSecurity": { + "message": "升级以获得全面的安全防护" + }, + "premiumGivesMoreTools": { + "message": "高级版为您提供更多工具,助您保障安全、高效工作并掌控一切。" + }, + "explorePremium": { + "message": "探索高级版" + }, "loadingVault": { "message": "正在加载密码库" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 370c147871b..600447a29f3 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -595,7 +595,7 @@ "message": "檢視全部" }, "viewLess": { - "message": "View less" + "message": "顯示較少" }, "viewLogin": { "message": "檢視登入" @@ -5809,6 +5809,15 @@ "upgradeToPremium": { "message": "升級到 Premium" }, + "upgradeCompleteSecurity": { + "message": "Upgrade for complete security" + }, + "premiumGivesMoreTools": { + "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + }, + "explorePremium": { + "message": "Explore Premium" + }, "loadingVault": { "message": "正在載入密碼庫" }, From fd1155ae581b046fc0e300ef9b04cf7b6d43f426 Mon Sep 17 00:00:00 2001 From: Jason Ng <jng@bitwarden.com> Date: Tue, 18 Nov 2025 12:38:18 -0500 Subject: [PATCH 167/249] [PM-27103] Add URL Check to Send (#17056) * add dangerousPatters check to api service --- libs/common/src/platform/misc/utils.spec.ts | 26 +++++++++++ libs/common/src/platform/misc/utils.ts | 49 +++++++++++++++++++++ libs/common/src/services/api.service.ts | 10 ++++- 3 files changed, 84 insertions(+), 1 deletion(-) diff --git a/libs/common/src/platform/misc/utils.spec.ts b/libs/common/src/platform/misc/utils.spec.ts index 9f01db61fa6..664c6e22b3a 100644 --- a/libs/common/src/platform/misc/utils.spec.ts +++ b/libs/common/src/platform/misc/utils.spec.ts @@ -689,6 +689,32 @@ describe("Utils Service", () => { }); }); + describe("invalidUrlPatterns", () => { + it("should return false if no invalid patterns are found", () => { + const urlString = "https://www.example.com/api/my/account/status"; + + const actual = Utils.invalidUrlPatterns(urlString); + + expect(actual).toBe(false); + }); + + it("should return true if an invalid pattern is found", () => { + const urlString = "https://www.example.com/api/%2e%2e/secret"; + + const actual = Utils.invalidUrlPatterns(urlString); + + expect(actual).toBe(true); + }); + + it("should return true if an invalid pattern is found in a param", () => { + const urlString = "https://www.example.com/api/history?someToken=../secret"; + + const actual = Utils.invalidUrlPatterns(urlString); + + expect(actual).toBe(true); + }); + }); + describe("getUrl", () => { it("assumes a http protocol if no protocol is specified", () => { const urlString = "www.exampleapp.com.au:4000"; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 5f977da3979..136b0ac394f 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -612,6 +612,55 @@ export class Utils { return path.normalize(decodeURIComponent(denormalizedPath)).replace(/^(\.\.(\/|\\|$))+/, ""); } + /** + * Validates an url checking against invalid patterns + * @param url + * @returns true if invalid patterns found, false if safe + */ + static invalidUrlPatterns(url: string): boolean { + const invalidUrlPatterns = ["..", "%2e", "\\", "%5c"]; + + const decodedUrl = decodeURIComponent(url.toLocaleLowerCase()); + + // Check URL for invalidUrl patterns across entire URL + if (invalidUrlPatterns.some((p) => decodedUrl.includes(p))) { + return true; + } + + // Check for additional invalid patterns inside URL params + if (decodedUrl.includes("?")) { + const hasInvalidParams = this.validateQueryParameters(decodedUrl); + if (hasInvalidParams) { + return true; + } + } + + return false; + } + + /** + * Validates query parameters for additional invalid patterns + * @param url - The URL containing query parameters + * @returns true if invalid patterns found, false if safe + */ + private static validateQueryParameters(url: string): boolean { + try { + let queryString: string; + + if (url.includes("?")) { + queryString = url.split("?")[1]; + } else { + return false; + } + + const paramInvalidPatterns = ["/", "%2f", "#", "%23"]; + + return paramInvalidPatterns.some((p) => queryString.includes(p)); + } catch (error) { + throw new Error(`Error validating query parameters: ${error}`); + } + } + private static isMobile(win: Window) { let mobile = false; ((a) => { diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 8314e44e75f..633c7eedcc4 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1588,8 +1588,16 @@ export class ApiService implements ApiServiceAbstraction { ); apiUrl = Utils.isNullOrWhitespace(apiUrl) ? env.getApiUrl() : apiUrl; - // Prevent directory traversal from malicious paths const pathParts = path.split("?"); + // Check for path traversal patterns from any URL. + const fullUrlPath = apiUrl + pathParts[0] + (pathParts.length > 1 ? `?${pathParts[1]}` : ""); + + const isInvalidUrl = Utils.invalidUrlPatterns(fullUrlPath); + if (isInvalidUrl) { + throw new Error("The request URL contains dangerous patterns."); + } + + // Prevent directory traversal from malicious paths const requestUrl = apiUrl + Utils.normalizePath(pathParts[0]) + (pathParts.length > 1 ? `?${pathParts[1]}` : ""); From 02ef4e72de615ca6fff7319398e69e244bb3a9be Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Tue, 18 Nov 2025 17:54:35 +0000 Subject: [PATCH 168/249] Bumped Desktop client to 2025.11.2 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4639a0b636d..2633f3d5909 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.11.1", + "version": "2025.11.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index c11c8f08cd0..0a4204bf233 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.11.1", + "version": "2025.11.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.11.1", + "version": "2025.11.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index d4800009de9..765bc771b9e 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.11.1", + "version": "2025.11.2", "author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index b7b625fdde2..67d3b687862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -280,7 +280,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.11.1", + "version": "2025.11.2", "hasInstallScript": true, "license": "GPL-3.0" }, From b952e6ea445314323f9342dcee96e3c9c7576fcc Mon Sep 17 00:00:00 2001 From: Will Martin <contact@willmartian.com> Date: Tue, 18 Nov 2025 13:08:21 -0500 Subject: [PATCH 169/249] [PM-28071] add prod test domain for phishing detection (#17450) --- .../dirt/phishing-detection/services/phishing-data.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts index cb76a1cc354..6e1bf07c647 100644 --- a/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts +++ b/apps/browser/src/dirt/phishing-detection/services/phishing-data.service.ts @@ -58,6 +58,7 @@ export class PhishingDataService { new Set( (state?.domains?.filter((line) => line.trim().length > 0) ?? []).concat( this._testDomains, + "phishing.testcategory.com", // Included for QA to test in prod ), ), ), From df03664827c3f77c3d0eff8b7c6cd2efd83721b8 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik <jprusik@users.noreply.github.com> Date: Tue, 18 Nov 2025 14:49:12 -0500 Subject: [PATCH 170/249] [PM-27915] Add additional global styling collision defenses for pseudo-elements (#17340) * add additional global styling collision defenses for pseudo-elements * move internal stylesheet into closed shadow root --- .../autofill-inline-menu-content.service.ts | 26 ---------- .../autofill-inline-menu-iframe-element.ts | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index b550ae203d5..be93e863275 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -240,8 +240,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte this.buttonElement = globalThis.document.createElement(customElementName); this.buttonElement.setAttribute("popover", "manual"); - - this.createInternalStyleNode(this.buttonElement); } /** @@ -270,30 +268,6 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte this.listElement = globalThis.document.createElement(customElementName); this.listElement.setAttribute("popover", "manual"); - - this.createInternalStyleNode(this.listElement); - } - - /** - * Builds and prepends an internal stylesheet to the container node with rules - * to prevent targeting by the host's global styling rules. This should only be - * used for pseudo elements such as `::backdrop` or `::before`. All other - * styles should be applied inline upon the parent container itself. - */ - private createInternalStyleNode(parent: HTMLElement) { - const css = document.createTextNode(` - ${parent.tagName}::backdrop { - background: none !important; - pointer-events: none !important; - } - ${parent.tagName}::before, ${parent.tagName}::after { - content:"" !important; - } - `); - const style = globalThis.document.createElement("style"); - style.setAttribute("type", "text/css"); - style.appendChild(css); - parent.prepend(style); } /** diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe-element.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe-element.ts index 2fea65a7f01..3e2b364b17b 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe-element.ts @@ -8,7 +8,10 @@ export class AutofillInlineMenuIframeElement { iframeTitle: string, ariaAlert?: string, ) { + const style = this.createInternalStyleNode(); const shadow: ShadowRoot = element.attachShadow({ mode: "closed" }); + shadow.prepend(style); + const autofillInlineMenuIframeService = new AutofillInlineMenuIframeService( shadow, portName, @@ -18,4 +21,50 @@ export class AutofillInlineMenuIframeElement { ); autofillInlineMenuIframeService.initMenuIframe(); } + + /** + * Builds and prepends an internal stylesheet to the container node with rules + * to prevent targeting by the host's global styling rules. This should only be + * used for pseudo elements such as `::backdrop` or `::before`. All other + * styles should be applied inline upon the parent container itself for improved + * specificity priority. + */ + private createInternalStyleNode() { + const css = document.createTextNode(` + :host::backdrop, + :host::before, + :host::after { + all: initial !important; + backdrop-filter: none !important; + filter: none !important; + inset: auto !important; + touch-action: auto !important; + user-select: text !important; + display: none !important; + position: relative !important; + top: auto !important; + right: auto !important; + bottom: auto !important; + left: auto !important; + transform: none !important; + transform-origin: 50% 50% !important; + opacity: 1 !important; + mix-blend-mode: normal !important; + isolation: isolate !important; + z-index: 0 !important; + background: none !important; + background-color: transparent !important; + background-image: none !important; + width: 0 !important; + height: 0 !important; + content: "" !important; + pointer-events: all !important; + } + `); + const style = globalThis.document.createElement("style"); + style.setAttribute("type", "text/css"); + style.appendChild(css); + + return style; + } } From bf8976ca66af75f6132b962b6b43b6734442e5bf Mon Sep 17 00:00:00 2001 From: gitclonebrian <235774926+gitclonebrian@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:27:21 -0500 Subject: [PATCH 171/249] [BRE-1333] Added permissions to token generation (#17471) --- .github/workflows/crowdin-pull.yml | 2 ++ .github/workflows/sdk-breaking-change-check.yml | 2 ++ .github/workflows/version-auto-bump.yml | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 19532493071..311737a2c0e 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -54,6 +54,8 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + permission-contents: write # for creating, committing to, and pushing new branches + permission-pull-requests: write # for generating pull requests - name: Checkout repo uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/.github/workflows/sdk-breaking-change-check.yml b/.github/workflows/sdk-breaking-change-check.yml index 759f2292d2a..1b9653417f2 100644 --- a/.github/workflows/sdk-breaking-change-check.yml +++ b/.github/workflows/sdk-breaking-change-check.yml @@ -58,6 +58,8 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + permission-actions: read # for reading and downloading the artifacts for a workflow run + - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index 9ff252d2fe8..d807dd046d3 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -36,6 +36,7 @@ jobs: with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} + permission-contents: write # for committing and pushing to the current branch - name: Check out target ref uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 From fde1e26ad9d26bca7fd925fb863c4fea32012b6f Mon Sep 17 00:00:00 2001 From: Kyle Denney <4227399+kdenney@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:24:36 -0600 Subject: [PATCH 172/249] [PM-28370] fix defect for self-hosted metadata (#17464) --- .../src/services/jslib-services.module.ts | 2 +- .../billing-api.service.abstraction.ts | 4 +++ .../billing/services/billing-api.service.ts | 14 ++++++++++ .../organization-metadata.service.spec.ts | 28 ++++++++++++++++++- .../organization-metadata.service.ts | 6 +++- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 9dbc6679963..b1215654cfd 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1451,7 +1451,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationMetadataServiceAbstraction, useClass: DefaultOrganizationMetadataService, - deps: [BillingApiServiceAbstraction, ConfigService], + deps: [BillingApiServiceAbstraction, ConfigService, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: BillingAccountProfileStateService, diff --git a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts index ef01c98ecb5..dcb395ef85c 100644 --- a/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/billing-api.service.abstraction.ts @@ -25,6 +25,10 @@ export abstract class BillingApiServiceAbstraction { organizationId: OrganizationId, ): Promise<OrganizationBillingMetadataResponse>; + abstract getOrganizationBillingMetadataVNextSelfHost( + organizationId: OrganizationId, + ): Promise<OrganizationBillingMetadataResponse>; + abstract getPlans(): Promise<ListResponse<PlanResponse>>; abstract getPremiumPlan(): Promise<PremiumPlanResponse>; diff --git a/libs/common/src/billing/services/billing-api.service.ts b/libs/common/src/billing/services/billing-api.service.ts index 673d4a9784e..ae6913e545c 100644 --- a/libs/common/src/billing/services/billing-api.service.ts +++ b/libs/common/src/billing/services/billing-api.service.ts @@ -62,6 +62,20 @@ export class BillingApiService implements BillingApiServiceAbstraction { return new OrganizationBillingMetadataResponse(r); } + async getOrganizationBillingMetadataVNextSelfHost( + organizationId: OrganizationId, + ): Promise<OrganizationBillingMetadataResponse> { + const r = await this.apiService.send( + "GET", + "/organizations/" + organizationId + "/billing/vnext/self-host/metadata", + null, + true, + true, + ); + + return new OrganizationBillingMetadataResponse(r); + } + async getPlans(): Promise<ListResponse<PlanResponse>> { const r = await this.apiService.send("GET", "/plans", null, true, true); return new ListResponse(r, PlanResponse); diff --git a/libs/common/src/billing/services/organization/organization-metadata.service.spec.ts b/libs/common/src/billing/services/organization/organization-metadata.service.spec.ts index c67f4aed175..a2b012eb161 100644 --- a/libs/common/src/billing/services/organization/organization-metadata.service.spec.ts +++ b/libs/common/src/billing/services/organization/organization-metadata.service.spec.ts @@ -4,6 +4,7 @@ import { BehaviorSubject, firstValueFrom } from "rxjs"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { newGuid } from "@bitwarden/guid"; import { FeatureFlag } from "../../../enums/feature-flag.enum"; @@ -15,6 +16,7 @@ describe("DefaultOrganizationMetadataService", () => { let service: DefaultOrganizationMetadataService; let billingApiService: jest.Mocked<BillingApiServiceAbstraction>; let configService: jest.Mocked<ConfigService>; + let platformUtilsService: jest.Mocked<PlatformUtilsService>; let featureFlagSubject: BehaviorSubject<boolean>; const mockOrganizationId = newGuid() as OrganizationId; @@ -33,11 +35,17 @@ describe("DefaultOrganizationMetadataService", () => { beforeEach(() => { billingApiService = mock<BillingApiServiceAbstraction>(); configService = mock<ConfigService>(); + platformUtilsService = mock<PlatformUtilsService>(); featureFlagSubject = new BehaviorSubject<boolean>(false); configService.getFeatureFlag$.mockReturnValue(featureFlagSubject.asObservable()); + platformUtilsService.isSelfHost.mockReturnValue(false); - service = new DefaultOrganizationMetadataService(billingApiService, configService); + service = new DefaultOrganizationMetadataService( + billingApiService, + configService, + platformUtilsService, + ); }); afterEach(() => { @@ -142,6 +150,24 @@ describe("DefaultOrganizationMetadataService", () => { expect(result3).toEqual(mockResponse1); expect(result4).toEqual(mockResponse2); }); + + it("calls getOrganizationBillingMetadataVNextSelfHost when feature flag is on and isSelfHost is true", async () => { + platformUtilsService.isSelfHost.mockReturnValue(true); + const mockResponse = createMockMetadataResponse(true, 25); + billingApiService.getOrganizationBillingMetadataVNextSelfHost.mockResolvedValue( + mockResponse, + ); + + const result = await firstValueFrom(service.getOrganizationMetadata$(mockOrganizationId)); + + expect(platformUtilsService.isSelfHost).toHaveBeenCalled(); + expect(billingApiService.getOrganizationBillingMetadataVNextSelfHost).toHaveBeenCalledWith( + mockOrganizationId, + ); + expect(billingApiService.getOrganizationBillingMetadataVNext).not.toHaveBeenCalled(); + expect(billingApiService.getOrganizationBillingMetadata).not.toHaveBeenCalled(); + expect(result).toEqual(mockResponse); + }); }); describe("shareReplay behavior", () => { diff --git a/libs/common/src/billing/services/organization/organization-metadata.service.ts b/libs/common/src/billing/services/organization/organization-metadata.service.ts index fe96f0d984c..5ce87262c4b 100644 --- a/libs/common/src/billing/services/organization/organization-metadata.service.ts +++ b/libs/common/src/billing/services/organization/organization-metadata.service.ts @@ -1,6 +1,7 @@ import { BehaviorSubject, combineLatest, from, Observable, shareReplay, switchMap } from "rxjs"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { FeatureFlag } from "../../../enums/feature-flag.enum"; import { ConfigService } from "../../../platform/abstractions/config/config.service"; @@ -17,6 +18,7 @@ export class DefaultOrganizationMetadataService implements OrganizationMetadataS constructor( private billingApiService: BillingApiServiceAbstraction, private configService: ConfigService, + private platformUtilsService: PlatformUtilsService, ) {} private refreshMetadataTrigger = new BehaviorSubject<void>(undefined); @@ -67,7 +69,9 @@ export class DefaultOrganizationMetadataService implements OrganizationMetadataS featureFlagEnabled: boolean, ): Promise<OrganizationBillingMetadataResponse> { return featureFlagEnabled - ? await this.billingApiService.getOrganizationBillingMetadataVNext(organizationId) + ? this.platformUtilsService.isSelfHost() + ? await this.billingApiService.getOrganizationBillingMetadataVNextSelfHost(organizationId) + : await this.billingApiService.getOrganizationBillingMetadataVNext(organizationId) : await this.billingApiService.getOrganizationBillingMetadata(organizationId); } } From 84ce21643dc3789f36b773012768e854a08d7530 Mon Sep 17 00:00:00 2001 From: Alex <55413326+AlexRubik@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:27:40 -0500 Subject: [PATCH 173/249] text and style (#17439) --- .../access-intelligence/activity/all-activity.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html index ffc67028b77..c9b930b3b50 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/all-activity.component.html @@ -80,11 +80,11 @@ @else { <li class="tw-col-span-1"> <dirt-activity-card - [title]="'applicationsNeedingReview' | i18n" + [title]="'reviewNewApplications' | i18n" [cardMetrics]="'newApplicationsWithCount' | i18n: newApplicationsCount" [metricDescription]="'newApplicationsDescription' | i18n" [iconClass]="'bwi-exclamation-triangle'" - [iconColorClass]="'tw-text-muted'" + [iconColorClass]="'tw-text-warning'" [buttonText]="'reviewNow' | i18n" [buttonType]="'primary'" (buttonClick)="onReviewNewApplications()" From 64bfbf274aa4e33447445f5dcc32311b16119b01 Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Wed, 19 Nov 2025 00:18:10 +0000 Subject: [PATCH 174/249] Bumped client version(s) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- package-lock.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 82d2ad7ab7a..72e112e62f7 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.11.0", + "version": "2025.11.1", "scripts": { "build": "npm run build:chrome", "build:bit": "npm run build:bit:chrome", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index d44a3d2a2e7..3d8f648daca 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.11.0", + "version": "2025.11.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index b6381201c7d..73403d2321e 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "Bitwarden", - "version": "2025.11.0", + "version": "2025.11.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/package-lock.json b/package-lock.json index 67d3b687862..44e46a5d41b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -194,7 +194,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.11.0" + "version": "2025.11.1" }, "apps/cli": { "name": "@bitwarden/cli", From e32309260cf85ac2c3fc7530aa2d4866d862be08 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 05:39:11 -0600 Subject: [PATCH 175/249] [deps] Platform: Update @types/node-forge to v1.3.14 (#17490) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44e46a5d41b..ccfad167c86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,7 +114,7 @@ "@types/lunr": "2.3.7", "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", - "@types/node-forge": "1.3.11", + "@types/node-forge": "1.3.14", "@types/papaparse": "5.5.0", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", @@ -14463,9 +14463,9 @@ } }, "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "license": "MIT", "dependencies": { "@types/node": "*" diff --git a/package.json b/package.json index 52a7d1c60a2..757b7fec628 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@types/lunr": "2.3.7", "@types/node": "22.19.1", "@types/node-fetch": "2.6.4", - "@types/node-forge": "1.3.11", + "@types/node-forge": "1.3.14", "@types/papaparse": "5.5.0", "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", From 58727e884302feb8b835b7aca2b2b019870f870f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 05:39:33 -0600 Subject: [PATCH 176/249] [deps] Platform: Update @types/chrome to v0.1.28 (#17489) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccfad167c86..674dcabf122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,7 +100,7 @@ "@storybook/theming": "8.6.12", "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", - "@types/chrome": "0.1.27", + "@types/chrome": "0.1.28", "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", @@ -13957,9 +13957,9 @@ } }, "node_modules/@types/chrome": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.27.tgz", - "integrity": "sha512-pkkCb0Ft8X+Igi751POzT+YqchSxUCtB6s4Gs6ttgSj8qzJga/qlJMgSW1mKxuQTW4i0sTqQbqVtzXDS5AU+4A==", + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.28.tgz", + "integrity": "sha512-wANMmVt9H8UJeRsk4vlk5IVTTUIdk0J6CJC2ER60fGHTJOFVMuXpGhCqs6fUGw3m9pF1eXEvi+6ejlQZrtGA4A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 757b7fec628..8322d065eba 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@storybook/theming": "8.6.12", "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", - "@types/chrome": "0.1.27", + "@types/chrome": "0.1.28", "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", From 90ca6bf2cd769934ca778fb5a124939e9897823c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:00:49 +0000 Subject: [PATCH 177/249] [deps]: Update codecov/test-results-action action to v1.1.1 (#17493) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 71f8e7c9155..ed967e63b5a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: fail-on-error: true - name: Upload results to codecov.io - uses: codecov/test-results-action@f2dba722c67b86c6caa034178c6e4d35335f6706 # v1.1.0 + uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 - name: Upload test coverage uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 From db16c201b879c8aa4c1a52a79cc2aa229a808cb2 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Wed, 19 Nov 2025 08:07:57 -0700 Subject: [PATCH 178/249] Align Desktop Native's Rust CI checks with SDK (#17261) * clean crate deps * update lint workflow * add rustfmt.toml * apply rust fmt * missed one * fix lint of lint lol * more deps platform fixes * fix macos_provider * some more deps clean * more cleanup * add --all-targets * remove another unused dep * generate index.d.ts * fix whitespace * fix split comment in biometric * formatting comment in biometric_v2 * apply fmt --- .github/workflows/lint.yml | 24 ++- apps/desktop/desktop_native/Cargo.lock | 200 ------------------ apps/desktop/desktop_native/Cargo.toml | 2 - .../autotype/src/windows/type_input.rs | 15 +- .../autotype/src/windows/window_title.rs | 16 +- .../src/windows/crypto.rs | 3 +- .../src/windows/impersonate.rs | 3 +- .../src/windows/log.rs | 6 +- .../src/windows/main.rs | 10 +- .../chromium_importer/Cargo.toml | 17 +- .../chromium_importer/src/chromium/mod.rs | 20 +- .../src/chromium/platform/linux.rs | 10 +- .../src/chromium/platform/macos.rs | 7 +- .../src/chromium/platform/windows/abe.rs | 6 +- .../src/chromium/platform/windows/mod.rs | 16 +- .../chromium/platform/windows/signature.rs | 3 +- .../chromium_importer/src/metadata.rs | 2 +- .../chromium_importer/src/util.rs | 43 ++-- apps/desktop/desktop_native/core/Cargo.toml | 29 +-- .../desktop_native/core/src/biometric/mod.rs | 10 +- .../desktop_native/core/src/biometric/unix.rs | 10 +- .../core/src/biometric/windows.rs | 7 +- .../core/src/biometric_v2/linux.rs | 27 +-- .../core/src/biometric_v2/mod.rs | 7 +- .../core/src/biometric_v2/windows.rs | 69 +++--- .../core/src/biometric_v2/windows_focus.rs | 32 +-- .../desktop_native/core/src/crypto/crypto.rs | 6 +- apps/desktop/desktop_native/core/src/error.rs | 11 - .../desktop_native/core/src/ipc/mod.rs | 8 +- .../desktop_native/core/src/ipc/server.rs | 19 +- .../desktop_native/core/src/password/macos.rs | 3 +- .../desktop_native/core/src/password/unix.rs | 6 +- .../core/src/password/windows.rs | 3 +- .../core/src/process_isolation/linux.rs | 8 +- .../core/src/secure_memory/dpapi.rs | 5 +- .../secure_memory/encrypted_memory_store.rs | 4 +- .../src/secure_memory/secure_key/crypto.rs | 6 +- .../src/secure_memory/secure_key/dpapi.rs | 7 +- .../src/secure_memory/secure_key/keyctl.rs | 13 +- .../secure_memory/secure_key/memfd_secret.rs | 11 +- .../src/secure_memory/secure_key/mlock.rs | 7 +- .../core/src/secure_memory/secure_key/mod.rs | 27 ++- .../desktop_native/core/src/ssh_agent/mod.rs | 8 +- .../ssh_agent/named_pipe_listener_stream.rs | 5 +- .../peercred_unix_listener_stream.rs | 12 +- .../core/src/ssh_agent/peerinfo/models.rs | 7 +- .../desktop_native/core/src/ssh_agent/unix.rs | 3 +- .../core/src/ssh_agent/windows.rs | 1 + .../desktop_native/macos_provider/Cargo.toml | 7 +- apps/desktop/desktop_native/napi/Cargo.toml | 4 - apps/desktop/desktop_native/napi/index.d.ts | 23 +- apps/desktop/desktop_native/napi/src/lib.rs | 37 ++-- apps/desktop/desktop_native/objc/Cargo.toml | 8 +- .../process_isolation/Cargo.toml | 2 +- .../process_isolation/src/lib.rs | 3 +- apps/desktop/desktop_native/proxy/Cargo.toml | 1 - apps/desktop/desktop_native/proxy/src/main.rs | 10 +- apps/desktop/desktop_native/rustfmt.toml | 7 + .../windows_plugin_authenticator/src/lib.rs | 11 +- 59 files changed, 382 insertions(+), 505 deletions(-) create mode 100644 apps/desktop/desktop_native/rustfmt.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c14abd7cd86..67186905390 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -98,12 +98,27 @@ jobs: with: persist-credentials: false + - name: Install Rust + uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # stable + with: + toolchain: stable + components: rustfmt, clippy + + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # stable + with: + toolchain: nightly + components: rustfmt + - name: Check Rust version run: rustup --version + - name: Cache cargo registry + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 + - name: Run cargo fmt working-directory: ./apps/desktop/desktop_native - run: cargo fmt --check + run: cargo +nightly fmt --check - name: Run Clippy working-directory: ./apps/desktop/desktop_native @@ -118,6 +133,13 @@ jobs: working-directory: ./apps/desktop/desktop_native run: cargo sort --workspace --check + - name: Install cargo-udeps + run: cargo install cargo-udeps --version 0.1.57 --locked + + - name: Cargo udeps + working-directory: ./apps/desktop/desktop_native + run: cargo +nightly udeps --workspace --all-features --all-targets + - name: Install cargo-deny uses: taiki-e/install-action@81ee1d48d9194cdcab880cbdc7d36e87d39874cb # v2.62.45 with: diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 4da82144305..e8cc9385bb2 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -684,17 +684,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "codespan-reporting" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" -dependencies = [ - "serde", - "termcolor", - "unicode-width", -] - [[package]] name = "colorchoice" version = "1.0.3" @@ -841,65 +830,6 @@ dependencies = [ "syn", ] -[[package]] -name = "cxx" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a71ea7f29c73f7ffa64c50b83c9fe4d3a6d4be89a86b009eb80d5a6d3429d741" -dependencies = [ - "cc", - "cxxbridge-cmd", - "cxxbridge-flags", - "cxxbridge-macro", - "foldhash", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36a8232661d66dcf713394726157d3cfe0a89bfc85f52d6e9f9bbc2306797fe7" -dependencies = [ - "cc", - "codespan-reporting", - "proc-macro2", - "quote", - "scratch", - "syn", -] - -[[package]] -name = "cxxbridge-cmd" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f44296c8693e9ea226a48f6a122727f77aa9e9e338380cb021accaeeb7ee279" -dependencies = [ - "clap", - "codespan-reporting", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f69c181c176981ae44ba9876e2ea41ce8e574c296b38d06925ce9214fb8e4" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8faff5d4467e0709448187df29ccbf3b0982cc426ee444a193f87b11afb565a8" -dependencies = [ - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "der" version = "0.7.10" @@ -921,27 +851,21 @@ dependencies = [ "ashpd", "base64", "bitwarden-russh", - "byteorder", "bytes", "cbc", "chacha20poly1305", "core-foundation", "desktop_objc", "dirs", - "ed25519", "futures", "homedir", "interprocess", - "keytar", "libc", "linux-keyutils", "memsec", "oo7", "pin-project", - "pkcs8", "rand 0.9.1", - "rsa", - "russh-cryptovec", "scopeguard", "secmem-proc", "security-framework", @@ -949,12 +873,10 @@ dependencies = [ "serde", "serde_json", "sha2", - "ssh-encoding", "ssh-key", "sysinfo", "thiserror 2.0.12", "tokio", - "tokio-stream", "tokio-util", "tracing", "typenum", @@ -972,18 +894,14 @@ version = "0.0.0" dependencies = [ "anyhow", "autotype", - "base64", "chromium_importer", "desktop_core", - "hex", "napi", "napi-build", "napi-derive", "serde", "serde_json", "tokio", - "tokio-stream", - "tokio-util", "tracing", "tracing-subscriber", "windows-registry", @@ -996,9 +914,7 @@ version = "0.0.0" dependencies = [ "anyhow", "cc", - "core-foundation", "glob", - "thiserror 2.0.12", "tokio", "tracing", ] @@ -1007,7 +923,6 @@ dependencies = [ name = "desktop_proxy" version = "0.0.0" dependencies = [ - "anyhow", "desktop_core", "embed_plist", "futures", @@ -1740,27 +1655,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "keytar" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d361c55fba09829ac620b040f5425bf239b1030c3d6820a84acac8da867dca4d" -dependencies = [ - "keytar-sys", -] - -[[package]] -name = "keytar-sys" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe908c6896705a1cb516cd6a5d956c63f08d95ace81b93253a98cd93e1e6a65a" -dependencies = [ - "cc", - "cxx", - "cxx-build", - "pkg-config", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1813,15 +1707,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" -dependencies = [ - "cc", -] - [[package]] name = "linux-keyutils" version = "0.2.4" @@ -1875,7 +1760,6 @@ dependencies = [ "serde", "serde_json", "tokio", - "tokio-util", "tracing", "tracing-oslog", "tracing-subscriber", @@ -2521,21 +2405,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkcs5" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" -dependencies = [ - "aes", - "cbc", - "der", - "pbkdf2", - "scrypt", - "sha2", - "spki", -] - [[package]] name = "pkcs8" version = "0.10.2" @@ -2543,8 +2412,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", - "pkcs5", - "rand_core 0.6.4", "spki", ] @@ -2923,27 +2790,12 @@ dependencies = [ "rustix 1.0.7", ] -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" -[[package]] -name = "salsa20" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" -dependencies = [ - "cipher", -] - [[package]] name = "scc" version = "2.4.0" @@ -2959,12 +2811,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scratch" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" - [[package]] name = "scroll" version = "0.12.0" @@ -2985,17 +2831,6 @@ dependencies = [ "syn", ] -[[package]] -name = "scrypt" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" -dependencies = [ - "pbkdf2", - "salsa20", - "sha2", -] - [[package]] name = "sdd" version = "3.0.10" @@ -3370,15 +3205,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" version = "0.5.1" @@ -3483,17 +3309,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.13" @@ -3693,12 +3508,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - [[package]] name = "uniffi" version = "0.28.3" @@ -4029,15 +3838,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys 0.59.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index ccf7c1f3796..d7afd44e9cd 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -39,7 +39,6 @@ futures = "=0.3.31" hex = "=0.4.3" homedir = "=0.3.4" interprocess = "=2.2.1" -keytar = "=0.1.6" libc = "=0.2.172" linux-keyutils = "=0.2.4" memsec = "=0.7.0" @@ -64,7 +63,6 @@ ssh-key = { version = "=0.6.7", default-features = false } sysinfo = "=0.35.0" thiserror = "=2.0.12" tokio = "=1.45.0" -tokio-stream = "=0.1.15" tokio-util = "=0.7.13" tracing = "=0.1.41" tracing-subscriber = { version = "=0.3.20", features = [ diff --git a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs index b757cf7752f..10f30f5ee4f 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/type_input.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/type_input.rs @@ -33,7 +33,8 @@ impl InputOperations for Win32InputOperations { /// Attempts to type the input text wherever the user's cursor is. /// /// `input` must be a vector of utf-16 encoded characters to insert. -/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super, Shift, letters a - Z +/// `keyboard_shortcut` must be a vector of Strings, where valid shortcut keys: Control, Alt, Super, +/// Shift, letters a - Z /// /// https://learn.microsoft.com/en-in/windows/win32/api/winuser/nf-winuser-sendinput pub(super) fn type_input(input: Vec<u16>, keyboard_shortcut: Vec<String>) -> Result<()> { @@ -234,16 +235,16 @@ where #[cfg(test)] mod tests { - //! For the mocking of the traits that are static methods, we need to use the `serial_test` crate - //! in order to mock those, since the mock expectations set have to be global in absence of a `self`. - //! More info: https://docs.rs/mockall/latest/mockall/#static-methods + //! For the mocking of the traits that are static methods, we need to use the `serial_test` + //! crate in order to mock those, since the mock expectations set have to be global in + //! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods - use super::*; - - use crate::windowing::MockErrorOperations; use serial_test::serial; use windows::Win32::Foundation::WIN32_ERROR; + use super::*; + use crate::windowing::MockErrorOperations; + #[test] fn get_alphabetic_hot_key_succeeds() { for c in ('a'..='z').chain('A'..='Z') { diff --git a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs index 58f06eb54c1..d56a811ab5c 100644 --- a/apps/desktop/desktop_native/autotype/src/windows/window_title.rs +++ b/apps/desktop/desktop_native/autotype/src/windows/window_title.rs @@ -127,8 +127,8 @@ where /// /// # Errors /// -/// - If the actual window title length (what the win32 API declares was written into the -/// buffer), is length zero and GetLastError() != 0 , return the GetLastError() message. +/// - If the actual window title length (what the win32 API declares was written into the buffer), +/// is length zero and GetLastError() != 0 , return the GetLastError() message. fn get_window_title<H, E>(window_handle: &H, expected_title_length: usize) -> Result<String> where H: WindowHandleOperations, @@ -169,17 +169,17 @@ where #[cfg(test)] mod tests { - //! For the mocking of the traits that are static methods, we need to use the `serial_test` crate - //! in order to mock those, since the mock expectations set have to be global in absence of a `self`. - //! More info: https://docs.rs/mockall/latest/mockall/#static-methods + //! For the mocking of the traits that are static methods, we need to use the `serial_test` + //! crate in order to mock those, since the mock expectations set have to be global in + //! absence of a `self`. More info: https://docs.rs/mockall/latest/mockall/#static-methods - use super::*; - - use crate::windowing::MockErrorOperations; use mockall::predicate; use serial_test::serial; use windows::Win32::Foundation::WIN32_ERROR; + use super::*; + use crate::windowing::MockErrorOperations; + #[test] #[serial] fn get_window_title_length_can_be_zero() { diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs index 094dbf94a67..c335a4b296a 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/crypto.rs @@ -95,7 +95,8 @@ pub(crate) fn decode_abe_key_blob(blob_data: &[u8]) -> Result<Vec<u8>> { let content_offset = content_len_offset + 4; let content = get_safe(blob_data, content_offset, content_len)?; - // When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds, Brave, possibly Edge + // When the size is exactly 32 bytes, it's a plain key. It's used in unbranded Chromium builds, + // Brave, possibly Edge if content_len == 32 { return Ok(content.to_vec()); } diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs index 5a5109b9d32..22006b8db14 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/impersonate.rs @@ -30,7 +30,8 @@ pub(crate) fn start_impersonating() -> Result<HANDLE> { // Need to enable SE_DEBUG_PRIVILEGE to enumerate and open SYSTEM processes enable_debug_privilege()?; - // Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication, so try several. + // Find a SYSTEM process and get its token. Not every SYSTEM process allows token duplication, + // so try several. let (token, pid, name) = find_system_process_with_token(get_system_pid_list())?; // Impersonate the SYSTEM process diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs index 7ee34a4160e..aa00a2f61b7 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/log.rs @@ -1,13 +1,13 @@ +use chromium_importer::config::{ENABLE_DEVELOPER_LOGGING, LOG_FILENAME}; use tracing::{error, level_filters::LevelFilter}; use tracing_subscriber::{ fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _, EnvFilter, Layer as _, }; -use chromium_importer::config::{ENABLE_DEVELOPER_LOGGING, LOG_FILENAME}; - pub(crate) fn init_logging() { if ENABLE_DEVELOPER_LOGGING { - // We only log to a file. It's impossible to see stdout/stderr when this exe is launched from ShellExecuteW. + // We only log to a file. It's impossible to see stdout/stderr when this exe is launched + // from ShellExecuteW. match std::fs::File::create(LOG_FILENAME) { Ok(file) => { let file_filter = EnvFilter::builder() diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs index e178a8accf7..560135b8ce4 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/src/windows/main.rs @@ -1,12 +1,14 @@ -use anyhow::{anyhow, Result}; -use clap::Parser; -use scopeguard::defer; use std::{ ffi::OsString, os::windows::{ffi::OsStringExt as _, io::AsRawHandle}, path::PathBuf, time::Duration, }; + +use anyhow::{anyhow, Result}; +use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME}; +use clap::Parser; +use scopeguard::defer; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::windows::named_pipe::{ClientOptions, NamedPipeClient}, @@ -25,8 +27,6 @@ use windows::Win32::{ UI::Shell::IsUserAnAdmin, }; -use chromium_importer::chromium::{verify_signature, ADMIN_TO_USER_PIPE_NAME}; - use super::{ crypto::{ decode_abe_key_blob, decode_base64, decrypt_with_dpapi_as_system, diff --git a/apps/desktop/desktop_native/chromium_importer/Cargo.toml b/apps/desktop/desktop_native/chromium_importer/Cargo.toml index 933b0a8dac3..4b02079bfdb 100644 --- a/apps/desktop/desktop_native/chromium_importer/Cargo.toml +++ b/apps/desktop/desktop_native/chromium_importer/Cargo.toml @@ -7,35 +7,38 @@ publish = { workspace = true } [dependencies] aes = { workspace = true } -aes-gcm = { workspace = true } anyhow = { workspace = true } async-trait = "=0.1.88" -base64 = { workspace = true } -cbc = { workspace = true, features = ["alloc"] } dirs = { workspace = true } hex = { workspace = true } -pbkdf2 = "=0.12.2" rand = { workspace = true } rusqlite = { version = "=0.37.0", features = ["bundled"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -sha1 = "=0.10.6" -tokio = { workspace = true, features = ["full"] } -tracing = { workspace = true } [target.'cfg(target_os = "macos")'.dependencies] +cbc = { workspace = true, features = ["alloc"] } +pbkdf2 = "=0.12.2" security-framework = { workspace = true } +sha1 = "=0.10.6" [target.'cfg(target_os = "windows")'.dependencies] +aes-gcm = { workspace = true } +base64 = { workspace = true } windows = { workspace = true, features = [ "Win32_Security_Cryptography", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", ] } verifysign = "=0.2.4" +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } [target.'cfg(target_os = "linux")'.dependencies] +cbc = { workspace = true, features = ["alloc"] } oo7 = { workspace = true } +pbkdf2 = "=0.12.2" +sha1 = "=0.10.6" [lints] workspace = true diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index 369e63e0ad1..e57b40b5778 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -1,6 +1,8 @@ -use std::collections::HashMap; -use std::path::{Path, PathBuf}; -use std::sync::LazyLock; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, + sync::LazyLock, +}; use anyhow::{anyhow, Result}; use async_trait::async_trait; @@ -10,11 +12,10 @@ use rusqlite::{params, Connection}; mod platform; +pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS; #[cfg(target_os = "windows")] pub use platform::*; -pub(crate) use platform::SUPPORTED_BROWSERS as PLATFORM_SUPPORTED_BROWSERS; - // // Public API // @@ -87,14 +88,15 @@ pub async fn import_logins( let local_logins = get_logins(&data_dir, profile_id, "Login Data") .map_err(|e| anyhow!("Failed to query logins: {}", e))?; - // This is not available in all browsers, but there's no harm in trying. If the file doesn't exist we just get an empty vector. + // This is not available in all browsers, but there's no harm in trying. If the file doesn't + // exist we just get an empty vector. let account_logins = get_logins(&data_dir, profile_id, "Login Data For Account") .map_err(|e| anyhow!("Failed to query logins: {}", e))?; // TODO: Do we need a better merge strategy? Maybe ignore duplicates at least? - // TODO: Should we also ignore an error from one of the two imports? If one is successful and the other fails, - // should we still return the successful ones? At the moment it doesn't fail for a missing file, only when - // something goes really wrong. + // TODO: Should we also ignore an error from one of the two imports? If one is successful and + // the other fails, should we still return the successful ones? At the moment it + // doesn't fail for a missing file, only when something goes really wrong. let all_logins = local_logins .into_iter() .chain(account_logins.into_iter()) diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs index 227dffdcca7..14e38797640 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs @@ -4,15 +4,17 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use oo7::XDG_SCHEMA_ATTRIBUTE; -use crate::chromium::{BrowserConfig, CryptoService, LocalState}; - -use crate::util; +use crate::{ + chromium::{BrowserConfig, CryptoService, LocalState}, + util, +}; // // Public API // -// TODO: It's possible that there might be multiple possible data directories, depending on the installation method (e.g., snap, flatpak, etc.). +// TODO: It's possible that there might be multiple possible data directories, depending on the +// installation method (e.g., snap, flatpak, etc.). pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs index c0e770c161b..5d0b4f0c75c 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs @@ -2,9 +2,10 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use security_framework::passwords::get_generic_password; -use crate::chromium::{BrowserConfig, CryptoService, LocalState}; - -use crate::util; +use crate::{ + chromium::{BrowserConfig, CryptoService, LocalState}, + util, +}; // // Public API diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/abe.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/abe.rs index 943727690f2..a76f7b95e5c 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/abe.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/abe.rs @@ -1,6 +1,6 @@ -use super::abe_config; -use anyhow::{anyhow, Result}; use std::{ffi::OsStr, os::windows::ffi::OsStrExt}; + +use anyhow::{anyhow, Result}; use tokio::{ io::{self, AsyncReadExt, AsyncWriteExt}, net::windows::named_pipe::{NamedPipeServer, ServerOptions}, @@ -14,6 +14,8 @@ use windows::{ Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_HIDE}, }; +use super::abe_config; + const WAIT_FOR_ADMIN_MESSAGE_TIMEOUT_SECS: u64 = 30; fn start_tokio_named_pipe_server<F>( diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs index 867104d9bfd..9cc89ed2161 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs @@ -1,11 +1,14 @@ +use std::path::{Path, PathBuf}; + use aes_gcm::{aead::Aead, Aes256Gcm, Key, KeyInit, Nonce}; use anyhow::{anyhow, Result}; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; -use std::path::{Path, PathBuf}; -use crate::chromium::{BrowserConfig, CryptoService, LocalState}; -use crate::util; +use crate::{ + chromium::{BrowserConfig, CryptoService, LocalState}, + util, +}; mod abe; mod abe_config; mod crypto; @@ -95,7 +98,8 @@ impl CryptoService for WindowsCryptoService { let (version, no_prefix) = util::split_encrypted_string_and_validate(encrypted, &["v10", "v20"])?; - // v10 is already stripped; Windows Chrome uses AES-GCM: [12 bytes IV][ciphertext][16 bytes auth tag] + // v10 is already stripped; Windows Chrome uses AES-GCM: [12 bytes IV][ciphertext][16 bytes + // auth tag] const IV_SIZE: usize = 12; const TAG_SIZE: usize = 16; const MIN_LENGTH: usize = IV_SIZE + TAG_SIZE; @@ -242,8 +246,8 @@ fn get_dist_admin_exe_path(current_exe_full_path: &Path) -> Result<PathBuf> { Ok(admin_exe) } -// Try to find bitwarden_chromium_import_helper.exe in debug build folders. This might not cover all the cases. -// Tested on `npm run electron` from apps/desktop and apps/desktop/desktop_native. +// Try to find bitwarden_chromium_import_helper.exe in debug build folders. This might not cover all +// the cases. Tested on `npm run electron` from apps/desktop and apps/desktop/desktop_native. fn get_debug_admin_exe_path() -> Result<PathBuf> { let current_dir = std::env::current_dir()?; let folder_name = current_dir diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs index d5d6c5d6d15..97cf57935b2 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/signature.rs @@ -1,5 +1,6 @@ -use anyhow::{anyhow, Result}; use std::path::Path; + +use anyhow::{anyhow, Result}; use tracing::{debug, info}; use verifysign::CodeSignVerifier; diff --git a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs index bfd7f184621..114c9f8df84 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/metadata.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/metadata.rs @@ -59,9 +59,9 @@ pub fn get_supported_importers<T: InstalledBrowserRetriever>( // Tests are cfg-gated based upon OS, and must be compiled/run on each OS for full coverage #[cfg(test)] mod tests { - use super::*; use std::collections::HashSet; + use super::*; use crate::chromium::{InstalledBrowserRetriever, SUPPORTED_BROWSER_MAP}; pub struct MockInstalledBrowserRetriever {} diff --git a/apps/desktop/desktop_native/chromium_importer/src/util.rs b/apps/desktop/desktop_native/chromium_importer/src/util.rs index f346d7e6dd0..2dbc6ed005b 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/util.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/util.rs @@ -32,7 +32,7 @@ pub(crate) fn split_encrypted_string_and_validate<'a>( } /// Decrypt using AES-128 in CBC mode. -#[cfg(any(target_os = "linux", target_os = "macos", test))] +#[cfg(any(target_os = "linux", target_os = "macos"))] pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>> { use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}; @@ -41,7 +41,8 @@ pub(crate) fn decrypt_aes_128_cbc(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> R .map_err(|e| anyhow!("Failed to decrypt: {}", e)) } -/// Derives a PBKDF2 key from the static "saltysalt" salt with the given password and iteration count. +/// Derives a PBKDF2 key from the static "saltysalt" salt with the given password and iteration +/// count. #[cfg(any(target_os = "linux", target_os = "macos"))] pub(crate) fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u8>> { use pbkdf2::{hmac::Hmac, pbkdf2}; @@ -55,27 +56,9 @@ pub(crate) fn derive_saltysalt(password: &[u8], iterations: u32) -> Result<Vec<u #[cfg(test)] mod tests { - use aes::cipher::{ - block_padding::Pkcs7, - generic_array::{sequence::GenericSequence, GenericArray}, - ArrayLength, BlockEncryptMut, KeyIvInit, - }; - - const LENGTH16: usize = 16; const LENGTH10: usize = 10; const LENGTH0: usize = 0; - fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> { - (0..length).map(|i| offset + i as u8 * increment).collect() - } - - fn generate_generic_array<N: ArrayLength<u8>>( - offset: u8, - increment: u8, - ) -> GenericArray<u8, N> { - GenericArray::generate(|i| offset + i as u8 * increment) - } - fn run_split_encrypted_string_test<'a, const N: usize>( successfully_split: bool, plaintext_to_encrypt: &'a str, @@ -144,8 +127,28 @@ mod tests { run_split_encrypted_string_and_validate_test(false, "v10EncryptMe!", &[]); } + #[cfg(any(target_os = "linux", target_os = "macos"))] #[test] fn test_decrypt_aes_128_cbc() { + use aes::cipher::{ + block_padding::Pkcs7, + generic_array::{sequence::GenericSequence, GenericArray}, + ArrayLength, BlockEncryptMut, KeyIvInit, + }; + + const LENGTH16: usize = 16; + + fn generate_generic_array<N: ArrayLength<u8>>( + offset: u8, + increment: u8, + ) -> GenericArray<u8, N> { + GenericArray::generate(|i| offset + i as u8 * increment) + } + + fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> { + (0..length).map(|i| offset + i as u8 * increment).collect() + } + let offset = 0; let increment = 1; diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index f6c9d669df6..dc9246f55c6 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -23,27 +23,15 @@ anyhow = { workspace = true } arboard = { workspace = true, features = ["wayland-data-control"] } base64 = { workspace = true } bitwarden-russh = { workspace = true } -byteorder = { workspace = true } bytes = { workspace = true } cbc = { workspace = true, features = ["alloc"] } chacha20poly1305 = { workspace = true } dirs = { workspace = true } -ed25519 = { workspace = true, features = ["pkcs8"] } futures = { workspace = true } -homedir = { workspace = true } interprocess = { workspace = true, features = ["tokio"] } memsec = { workspace = true, features = ["alloc_ext"] } -pin-project = { workspace = true } -pkcs8 = { workspace = true, features = ["alloc", "encryption", "pem"] } rand = { workspace = true } -rsa = { workspace = true } -russh-cryptovec = { workspace = true } -scopeguard = { workspace = true } -secmem-proc = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } sha2 = { workspace = true } -ssh-encoding = { workspace = true } ssh-key = { workspace = true, features = [ "encryption", "ed25519", @@ -53,13 +41,17 @@ ssh-key = { workspace = true, features = [ sysinfo = { workspace = true, features = ["windows"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["io-util", "sync", "macros", "net"] } -tokio-stream = { workspace = true, features = ["net"] } tokio-util = { workspace = true, features = ["codec"] } tracing = { workspace = true } typenum = { workspace = true } zeroizing-alloc = { workspace = true } [target.'cfg(windows)'.dependencies] +pin-project = { workspace = true } +scopeguard = { workspace = true } +secmem-proc = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } widestring = { workspace = true, optional = true } windows = { workspace = true, features = [ "Foundation", @@ -76,21 +68,20 @@ windows = { workspace = true, features = [ ], optional = true } windows-future = { workspace = true } -[target.'cfg(windows)'.dev-dependencies] -keytar = { workspace = true } - [target.'cfg(target_os = "macos")'.dependencies] core-foundation = { workspace = true, optional = true } +homedir = { workspace = true } +secmem-proc = { workspace = true } security-framework = { workspace = true, optional = true } security-framework-sys = { workspace = true, optional = true } desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] -oo7 = { workspace = true } +ashpd = { workspace = true } +homedir = { workspace = true } libc = { workspace = true } linux-keyutils = { workspace = true } -ashpd = { workspace = true } - +oo7 = { workspace = true } zbus = { workspace = true, optional = true } zbus_polkit = { workspace = true, optional = true } diff --git a/apps/desktop/desktop_native/core/src/biometric/mod.rs b/apps/desktop/desktop_native/core/src/biometric/mod.rs index e4d51f5da9a..937c67ff30a 100644 --- a/apps/desktop/desktop_native/core/src/biometric/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric/mod.rs @@ -86,11 +86,15 @@ impl KeyMaterial { #[cfg(test)] mod tests { - use crate::biometric::{decrypt, encrypt, KeyMaterial}; - use crate::crypto::CipherString; - use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; use std::str::FromStr; + use base64::{engine::general_purpose::STANDARD as base64_engine, Engine}; + + use crate::{ + biometric::{decrypt, encrypt, KeyMaterial}, + crypto::CipherString, + }; + fn key_material() -> KeyMaterial { KeyMaterial { os_key_part_b64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_owned(), diff --git a/apps/desktop/desktop_native/core/src/biometric/unix.rs b/apps/desktop/desktop_native/core/src/biometric/unix.rs index 0f6ff8f33dc..3f4f10a1fcf 100644 --- a/apps/desktop/desktop_native/core/src/biometric/unix.rs +++ b/apps/desktop/desktop_native/core/src/biometric/unix.rs @@ -1,18 +1,18 @@ use std::str::FromStr; -use anyhow::Result; +use anyhow::{anyhow, Result}; use base64::Engine; use rand::RngCore; use sha2::{Digest, Sha256}; use tracing::error; - -use crate::biometric::{base64_engine, KeyMaterial, OsDerivedKey}; use zbus::Connection; use zbus_polkit::policykit1::*; use super::{decrypt, encrypt}; -use crate::crypto::CipherString; -use anyhow::anyhow; +use crate::{ + biometric::{base64_engine, KeyMaterial, OsDerivedKey}, + crypto::CipherString, +}; /// The Unix implementation of the biometric trait. pub struct Biometric {} diff --git a/apps/desktop/desktop_native/core/src/biometric/windows.rs b/apps/desktop/desktop_native/core/src/biometric/windows.rs index 8013c21bf9a..f72282d9284 100644 --- a/apps/desktop/desktop_native/core/src/biometric/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric/windows.rs @@ -16,13 +16,12 @@ use windows::{ }; use windows_future::IAsyncOperation; +use super::{decrypt, encrypt, windows_focus::set_focus}; use crate::{ biometric::{KeyMaterial, OsDerivedKey}, crypto::CipherString, }; -use super::{decrypt, encrypt, windows_focus::set_focus}; - /// The Windows OS implementation of the biometric trait. pub struct Biometric {} @@ -61,7 +60,8 @@ impl super::BiometricTrait for Biometric { match ucv_available { UserConsentVerifierAvailability::Available => Ok(true), - UserConsentVerifierAvailability::DeviceBusy => Ok(true), // TODO: Look into removing this and making the check more ad-hoc + // TODO: look into removing this and making the check more ad-hoc + UserConsentVerifierAvailability::DeviceBusy => Ok(true), _ => Ok(false), } } @@ -133,7 +133,6 @@ fn random_challenge() -> [u8; 16] { #[cfg(test)] mod tests { use super::*; - use crate::biometric::BiometricTrait; #[test] diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs b/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs index 44cba4a9e5b..ff2abc0686b 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/linux.rs @@ -1,17 +1,19 @@ //! This file implements Polkit based system unlock. //! //! # Security -//! This section describes the assumed security model and security guarantees achieved. In the required security -//! guarantee is that a locked vault - a running app - cannot be unlocked when the device (user-space) -//! is compromised in this state. +//! This section describes the assumed security model and security guarantees achieved. In the +//! required security guarantee is that a locked vault - a running app - cannot be unlocked when the +//! device (user-space) is compromised in this state. //! -//! When first unlocking the app, the app sends the user-key to this module, which holds it in secure memory, -//! protected by memfd_secret. This makes it inaccessible to other processes, even if they compromise root, a kernel compromise -//! has circumventable best-effort protections. While the app is running this key is held in memory, even if locked. -//! When unlocking, the app will prompt the user via `polkit` to get a yes/no decision on whether to release the key to the app. +//! When first unlocking the app, the app sends the user-key to this module, which holds it in +//! secure memory, protected by memfd_secret. This makes it inaccessible to other processes, even if +//! they compromise root, a kernel compromise has circumventable best-effort protections. While the +//! app is running this key is held in memory, even if locked. When unlocking, the app will prompt +//! the user via `polkit` to get a yes/no decision on whether to release the key to the app. + +use std::sync::Arc; use anyhow::{anyhow, Result}; -use std::sync::Arc; use tokio::sync::Mutex; use tracing::{debug, warn}; use zbus::Connection; @@ -20,8 +22,8 @@ use zbus_polkit::policykit1::{AuthorityProxy, CheckAuthorizationFlags, Subject}; use crate::secure_memory::*; pub struct BiometricLockSystem { - // The userkeys that are held in memory MUST be protected from memory dumping attacks, to ensure - // locked vaults cannot be unlocked + // The userkeys that are held in memory MUST be protected from memory dumping attacks, to + // ensure locked vaults cannot be unlocked secure_memory: Arc<Mutex<crate::secure_memory::encrypted_memory_store::EncryptedMemoryStore>>, } @@ -88,8 +90,9 @@ impl super::BiometricTrait for BiometricLockSystem { } } -/// Perform a polkit authorization against the bitwarden unlock policy. Note: This relies on no custom -/// rules in the system skipping the authorization check, in which case this counts as UV / authentication. +/// Perform a polkit authorization against the bitwarden unlock policy. Note: This relies on no +/// custom rules in the system skipping the authorization check, in which case this counts as UV / +/// authentication. async fn polkit_authenticate_bitwarden_policy() -> Result<bool> { debug!("[Polkit] Authenticating / performing UV"); diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/mod.rs b/apps/desktop/desktop_native/core/src/biometric_v2/mod.rs index 669267b7829..55aee27dd33 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/mod.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/mod.rs @@ -17,8 +17,8 @@ pub trait BiometricTrait: Send + Sync { async fn authenticate(&self, hwnd: Vec<u8>, message: String) -> Result<bool>; /// Check if biometric authentication is available async fn authenticate_available(&self) -> Result<bool>; - /// Enroll a key for persistent unlock. If the implementation does not support persistent enrollment, - /// this function should do nothing. + /// Enroll a key for persistent unlock. If the implementation does not support persistent + /// enrollment, this function should do nothing. async fn enroll_persistent(&self, user_id: &str, key: &[u8]) -> Result<()>; /// Clear the persistent and ephemeral keys async fn unenroll(&self, user_id: &str) -> Result<()>; @@ -28,6 +28,7 @@ pub trait BiometricTrait: Send + Sync { async fn provide_key(&self, user_id: &str, key: &[u8]); /// Perform biometric unlock and return the key async fn unlock(&self, user_id: &str, hwnd: Vec<u8>) -> Result<Vec<u8>>; - /// Check if biometric unlock is available based on whether a key is present and whether authentication is possible + /// Check if biometric unlock is available based on whether a key is present and whether + /// authentication is possible async fn unlock_available(&self, user_id: &str) -> Result<bool>; } diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs index 043c2453cd0..32d2eb7e6e6 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/windows.rs @@ -2,38 +2,40 @@ //! //! There are two paths implemented here. //! The former via UV + ephemerally (but protected) keys. This only works after first unlock. -//! The latter via a signing API, that deterministically signs a challenge, from which a windows hello key is derived. This key -//! is used to encrypt the protected key. +//! The latter via a signing API, that deterministically signs a challenge, from which a windows +//! hello key is derived. This key is used to encrypt the protected key. //! //! # Security -//! The security goal is that a locked vault - a running app - cannot be unlocked when the device (user-space) -//! is compromised in this state. +//! The security goal is that a locked vault - a running app - cannot be unlocked when the device +//! (user-space) is compromised in this state. //! //! ## UV path -//! When first unlocking the app, the app sends the user-key to this module, which holds it in secure memory, -//! protected by DPAPI. This makes it inaccessible to other processes, unless they compromise the system administrator, or kernel. -//! While the app is running this key is held in memory, even if locked. When unlocking, the app will prompt the user via +//! When first unlocking the app, the app sends the user-key to this module, which holds it in +//! secure memory, protected by DPAPI. This makes it inaccessible to other processes, unless they +//! compromise the system administrator, or kernel. While the app is running this key is held in +//! memory, even if locked. When unlocking, the app will prompt the user via //! `windows_hello_authenticate` to get a yes/no decision on whether to release the key to the app. -//! Note: Further process isolation is needed here so that code cannot be injected into the running process, which may -//! circumvent DPAPI. +//! Note: Further process isolation is needed here so that code cannot be injected into the running +//! process, which may circumvent DPAPI. //! //! ## Sign path -//! In this scenario, when enrolling, the app sends the user-key to this module, which derives the windows hello key -//! with the Windows Hello prompt. This is done by signing a per-user challenge, which produces a deterministic -//! signature which is hashed to obtain a key. This key is used to encrypt and persist the vault unlock key (user key). +//! In this scenario, when enrolling, the app sends the user-key to this module, which derives the +//! windows hello key with the Windows Hello prompt. This is done by signing a per-user challenge, +//! which produces a deterministic signature which is hashed to obtain a key. This key is used to +//! encrypt and persist the vault unlock key (user key). //! -//! Since the keychain can be accessed by all user-space processes, the challenge is known to all userspace processes. -//! Therefore, to circumvent the security measure, the attacker would need to create a fake Windows-Hello prompt, and -//! get the user to confirm it. +//! Since the keychain can be accessed by all user-space processes, the challenge is known to all +//! userspace processes. Therefore, to circumvent the security measure, the attacker would need to +//! create a fake Windows-Hello prompt, and get the user to confirm it. use std::sync::{atomic::AtomicBool, Arc}; -use tracing::{debug, warn}; use aes::cipher::KeyInit; use anyhow::{anyhow, Result}; use chacha20poly1305::{aead::Aead, XChaCha20Poly1305, XNonce}; use sha2::{Digest, Sha256}; use tokio::sync::Mutex; +use tracing::{debug, warn}; use windows::{ core::{factory, h, Interface, HSTRING}, Security::{ @@ -74,8 +76,8 @@ struct WindowsHelloKeychainEntry { /// The Windows OS implementation of the biometric trait. pub struct BiometricLockSystem { - // The userkeys that are held in memory MUST be protected from memory dumping attacks, to ensure - // locked vaults cannot be unlocked + // The userkeys that are held in memory MUST be protected from memory dumping attacks, to + // ensure locked vaults cannot be unlocked secure_memory: Arc<Mutex<crate::secure_memory::dpapi::DpapiSecretKVStore>>, } @@ -114,12 +116,14 @@ impl super::BiometricTrait for BiometricLockSystem { } async fn enroll_persistent(&self, user_id: &str, key: &[u8]) -> Result<()> { - // Enrollment works by first generating a random challenge unique to the user / enrollment. Then, - // with the challenge and a Windows-Hello prompt, the "windows hello key" is derived. The windows - // hello key is used to encrypt the key to store with XChaCha20Poly1305. The bundle of nonce, - // challenge and wrapped-key are stored to the keychain + // Enrollment works by first generating a random challenge unique to the user / enrollment. + // Then, with the challenge and a Windows-Hello prompt, the "windows hello key" is + // derived. The windows hello key is used to encrypt the key to store with + // XChaCha20Poly1305. The bundle of nonce, challenge and wrapped-key are stored to + // the keychain - // Each enrollment (per user) has a unique challenge, so that the windows-hello key is unique + // Each enrollment (per user) has a unique challenge, so that the windows-hello key is + // unique let challenge: [u8; CHALLENGE_LENGTH] = rand::random(); // This key is unique to the challenge @@ -155,8 +159,8 @@ impl super::BiometricTrait for BiometricLockSystem { }); let mut secure_memory = self.secure_memory.lock().await; - // If the key is held ephemerally, always use UV API. Only use signing API if the key is not held - // ephemerally but the keychain holds it persistently. + // If the key is held ephemerally, always use UV API. Only use signing API if the key is not + // held ephemerally but the keychain holds it persistently. if secure_memory.has(user_id) { if windows_hello_authenticate("Unlock your vault".to_string()).await? { secure_memory @@ -175,7 +179,8 @@ impl super::BiometricTrait for BiometricLockSystem { &keychain_entry.wrapped_key, &keychain_entry.nonce, )?; - // The first unlock already sets the key for subsequent unlocks. The key may again be set externally after unlock finishes. + // The first unlock already sets the key for subsequent unlocks. The key may again be + // set externally after unlock finishes. secure_memory.put(user_id.to_string(), &decrypted_key.clone()); Ok(decrypted_key) } @@ -231,8 +236,8 @@ async fn windows_hello_authenticate_with_crypto( ) -> Result<[u8; XCHACHA20POLY1305_KEY_LENGTH]> { debug!("[Windows Hello] Authenticating to sign challenge"); - // Ugly hack: We need to focus the window via window focusing APIs until Microsoft releases a new API. - // This is unreliable, and if it does not work, the operation may fail + // Ugly hack: We need to focus the window via window focusing APIs until Microsoft releases a + // new API. This is unreliable, and if it does not work, the operation may fail let stop_focusing = Arc::new(AtomicBool::new(false)); let stop_focusing_clone = stop_focusing.clone(); let _ = std::thread::spawn(move || loop { @@ -243,8 +248,8 @@ async fn windows_hello_authenticate_with_crypto( break; } }); - // Only stop focusing once this function exits. The focus MUST run both during the initial creation - // with RequestCreateAsync, and also with the subsequent use with RequestSignAsync. + // Only stop focusing once this function exits. The focus MUST run both during the initial + // creation with RequestCreateAsync, and also with the subsequent use with RequestSignAsync. let _guard = scopeguard::guard((), |_| { stop_focusing.store(true, std::sync::atomic::Ordering::Relaxed); }); @@ -283,8 +288,8 @@ async fn windows_hello_authenticate_with_crypto( let signature_buffer = signature.Result()?; let signature_value = unsafe { as_mut_bytes(&signature_buffer)? }; - // The signature is deterministic based on the challenge and keychain key. Thus, it can be hashed to a key. - // It is unclear what entropy this key provides. + // The signature is deterministic based on the challenge and keychain key. Thus, it can be + // hashed to a key. It is unclear what entropy this key provides. let windows_hello_key = Sha256::digest(signature_value).into(); Ok(windows_hello_key) } diff --git a/apps/desktop/desktop_native/core/src/biometric_v2/windows_focus.rs b/apps/desktop/desktop_native/core/src/biometric_v2/windows_focus.rs index f3ffb6e4ebe..bf303c88e01 100644 --- a/apps/desktop/desktop_native/core/src/biometric_v2/windows_focus.rs +++ b/apps/desktop/desktop_native/core/src/biometric_v2/windows_focus.rs @@ -34,23 +34,25 @@ pub fn focus_security_prompt() { /// Sets focus to a window using a few unstable methods fn set_focus(hwnd: HWND) { unsafe { - // Windows REALLY does not like apps stealing focus, even if it is for fixing Windows-Hello bugs. - // The windows hello signing prompt NEEDS to be focused instantly, or it will error, but it does - // not focus itself. + // Windows REALLY does not like apps stealing focus, even if it is for fixing Windows-Hello + // bugs. The windows hello signing prompt NEEDS to be focused instantly, or it will + // error, but it does not focus itself. // This function implements forced focusing of windows using a few hacks. // The conditions to successfully foreground a window are: // All of the following conditions are true: - // The calling process belongs to a desktop application, not a UWP app or a Windows Store app designed for Windows 8 or 8.1. - // The foreground process has not disabled calls to SetForegroundWindow by a previous call to the LockSetForegroundWindow function. - // The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo). - // No menus are active. + // - The calling process belongs to a desktop application, not a UWP app or a Windows + // Store app designed for Windows 8 or 8.1. + // - The foreground process has not disabled calls to SetForegroundWindow by a previous + // call to the LockSetForegroundWindow function. + // - The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in + // SystemParametersInfo). No menus are active. // Additionally, at least one of the following conditions is true: - // The calling process is the foreground process. - // The calling process was started by the foreground process. - // There is currently no foreground window, and thus no foreground process. - // The calling process received the last input event. - // Either the foreground process or the calling process is being debugged. + // - The calling process is the foreground process. + // - The calling process was started by the foreground process. + // - There is currently no foreground window, and thus no foreground process. + // - The calling process received the last input event. + // - Either the foreground process or the calling process is being debugged. // Update the foreground lock timeout temporarily let mut old_timeout = 0; @@ -75,7 +77,8 @@ fn set_focus(hwnd: HWND) { ); }); - // Attach to the foreground thread once attached, we can foreground, even if in the background + // Attach to the foreground thread once attached, we can foreground, even if in the + // background let dw_current_thread = GetCurrentThreadId(); let dw_fg_thread = GetWindowThreadProcessId(GetForegroundWindow(), None); @@ -91,7 +94,8 @@ fn set_focus(hwnd: HWND) { } } -/// When restoring focus to the application window, we need a less aggressive method so the electron window doesn't get frozen. +/// When restoring focus to the application window, we need a less aggressive method so the electron +/// window doesn't get frozen. pub(crate) fn restore_focus(hwnd: HWND) { unsafe { let _ = SetForegroundWindow(hwnd); diff --git a/apps/desktop/desktop_native/core/src/crypto/crypto.rs b/apps/desktop/desktop_native/core/src/crypto/crypto.rs index d9e2aec3046..7991c87ca28 100644 --- a/apps/desktop/desktop_native/core/src/crypto/crypto.rs +++ b/apps/desktop/desktop_native/core/src/crypto/crypto.rs @@ -5,9 +5,8 @@ use aes::cipher::{ BlockEncryptMut, KeyIvInit, }; -use crate::error::{CryptoError, Result}; - use super::CipherString; +use crate::error::{CryptoError, Result}; pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) -> Result<Vec<u8>> { let iv = GenericArray::from_slice(iv); @@ -16,7 +15,8 @@ pub fn decrypt_aes256(iv: &[u8; 16], data: &[u8], key: GenericArray<u8, U32>) -> .decrypt_padded_mut::<Pkcs7>(&mut data) .map_err(|_| CryptoError::KeyDecrypt)?; - // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length + // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, + // we truncate to the subslice length let decrypted_len = decrypted_key_slice.len(); data.truncate(decrypted_len); diff --git a/apps/desktop/desktop_native/core/src/error.rs b/apps/desktop/desktop_native/core/src/error.rs index d70d8624018..c8d3ec02332 100644 --- a/apps/desktop/desktop_native/core/src/error.rs +++ b/apps/desktop/desktop_native/core/src/error.rs @@ -35,15 +35,4 @@ pub enum KdfParamError { InvalidParams(String), } -// Ensure that the error messages implement Send and Sync -#[cfg(test)] -const _: () = { - fn assert_send<T: Send>() {} - fn assert_sync<T: Sync>() {} - fn assert_all() { - assert_send::<Error>(); - assert_sync::<Error>(); - } -}; - pub type Result<T, E = Error> = std::result::Result<T, E>; diff --git a/apps/desktop/desktop_native/core/src/ipc/mod.rs b/apps/desktop/desktop_native/core/src/ipc/mod.rs index 5d4cc9e27f7..f806e395d10 100644 --- a/apps/desktop/desktop_native/core/src/ipc/mod.rs +++ b/apps/desktop/desktop_native/core/src/ipc/mod.rs @@ -49,7 +49,8 @@ pub fn path(name: &str) -> std::path::PathBuf { #[cfg(target_os = "macos")] { // When running in an unsandboxed environment, path is: /Users/<user>/ - // While running sandboxed, it's different: /Users/<user>/Library/Containers/com.bitwarden.desktop/Data + // While running sandboxed, it's different: + // /Users/<user>/Library/Containers/com.bitwarden.desktop/Data let mut home = dirs::home_dir().unwrap(); // Check if the app is sandboxed by looking for the Containers directory @@ -59,8 +60,9 @@ pub fn path(name: &str) -> std::path::PathBuf { // If the app is sanboxed, we need to use the App Group directory if let Some(position) = containers_position { - // We want to use App Groups in /Users/<user>/Library/Group Containers/LTZ2PFU5D6.com.bitwarden.desktop, - // so we need to remove all the components after the user. We can use the previous position to do this. + // We want to use App Groups in /Users/<user>/Library/Group + // Containers/LTZ2PFU5D6.com.bitwarden.desktop, so we need to remove all the + // components after the user. We can use the previous position to do this. while home.components().count() > position - 1 { home.pop(); } diff --git a/apps/desktop/desktop_native/core/src/ipc/server.rs b/apps/desktop/desktop_native/core/src/ipc/server.rs index 2762a832ac6..a65638303f1 100644 --- a/apps/desktop/desktop_native/core/src/ipc/server.rs +++ b/apps/desktop/desktop_native/core/src/ipc/server.rs @@ -3,9 +3,8 @@ use std::{ path::{Path, PathBuf}, }; -use futures::{SinkExt, StreamExt, TryFutureExt}; - use anyhow::Result; +use futures::{SinkExt, StreamExt, TryFutureExt}; use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions}; use tokio::{ io::{AsyncRead, AsyncWrite}, @@ -42,14 +41,17 @@ impl Server { /// /// # Parameters /// - /// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// - `client_to_server_send`: This [`mpsc::Sender<Message>`] will receive all the [`Message`]'s that the clients send to this server. + /// - `name`: The endpoint name to listen on. This name uniquely identifies the IPC connection + /// and must be the same for both the server and client. + /// - `client_to_server_send`: This [`mpsc::Sender<Message>`] will receive all the [`Message`]'s + /// that the clients send to this server. pub fn start( path: &Path, client_to_server_send: mpsc::Sender<Message>, ) -> Result<Self, Box<dyn Error>> { - // If the unix socket file already exists, we get an error when trying to bind to it. So we remove it first. - // Any processes that were using the old socket should remain connected to it but any new connections will use the new socket. + // If the unix socket file already exists, we get an error when trying to bind to it. So we + // remove it first. Any processes that were using the old socket should remain + // connected to it but any new connections will use the new socket. if !cfg!(windows) { let _ = std::fs::remove_file(path); } @@ -58,8 +60,9 @@ impl Server { let opts = ListenerOptions::new().name(name); let listener = opts.create_tokio()?; - // This broadcast channel is used for sending messages to all connected clients, and so the sender - // will be stored in the server while the receiver will be cloned and passed to each client handler. + // This broadcast channel is used for sending messages to all connected clients, and so the + // sender will be stored in the server while the receiver will be cloned and passed + // to each client handler. let (server_to_clients_send, server_to_clients_recv) = broadcast::channel::<String>(MESSAGE_CHANNEL_BUFFER); diff --git a/apps/desktop/desktop_native/core/src/password/macos.rs b/apps/desktop/desktop_native/core/src/password/macos.rs index 4f3a16ba4be..72d8ebeb425 100644 --- a/apps/desktop/desktop_native/core/src/password/macos.rs +++ b/apps/desktop/desktop_native/core/src/password/macos.rs @@ -1,9 +1,10 @@ -use crate::password::PASSWORD_NOT_FOUND; use anyhow::Result; use security_framework::passwords::{ delete_generic_password, get_generic_password, set_generic_password, }; +use crate::password::PASSWORD_NOT_FOUND; + #[allow(clippy::unused_async)] pub async fn get_password(service: &str, account: &str) -> Result<String> { let password = get_generic_password(service, account).map_err(convert_error)?; diff --git a/apps/desktop/desktop_native/core/src/password/unix.rs b/apps/desktop/desktop_native/core/src/password/unix.rs index b7595dca287..57b71adefed 100644 --- a/apps/desktop/desktop_native/core/src/password/unix.rs +++ b/apps/desktop/desktop_native/core/src/password/unix.rs @@ -1,9 +1,11 @@ -use crate::password::PASSWORD_NOT_FOUND; +use std::collections::HashMap; + use anyhow::{anyhow, Result}; use oo7::dbus::{self}; -use std::collections::HashMap; use tracing::info; +use crate::password::PASSWORD_NOT_FOUND; + pub async fn get_password(service: &str, account: &str) -> Result<String> { match get_password_new(service, account).await { Ok(res) => Ok(res), diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index ad09019f014..645620b444e 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -1,4 +1,3 @@ -use crate::password::PASSWORD_NOT_FOUND; use anyhow::{anyhow, Result}; use widestring::{U16CString, U16String}; use windows::{ @@ -12,6 +11,8 @@ use windows::{ }, }; +use crate::password::PASSWORD_NOT_FOUND; + const CRED_FLAGS_NONE: u32 = 0; #[allow(clippy::unused_async)] diff --git a/apps/desktop/desktop_native/core/src/process_isolation/linux.rs b/apps/desktop/desktop_native/core/src/process_isolation/linux.rs index bad348c93e2..263cc10b716 100644 --- a/apps/desktop/desktop_native/core/src/process_isolation/linux.rs +++ b/apps/desktop/desktop_native/core/src/process_isolation/linux.rs @@ -4,15 +4,15 @@ use libc::c_uint; use libc::{self, c_int}; use tracing::info; -// RLIMIT_CORE is the maximum size of a core dump file. Setting both to 0 disables core dumps, on crashes -// https://github.com/torvalds/linux/blob/1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0/include/uapi/asm-generic/resource.h#L20 +// RLIMIT_CORE is the maximum size of a core dump file. Setting both to 0 disables core dumps, on +// crashes https://github.com/torvalds/linux/blob/1613e604df0cd359cf2a7fbd9be7a0bcfacfabd0/include/uapi/asm-generic/resource.h#L20 #[cfg(target_env = "musl")] const RLIMIT_CORE: c_int = 4; #[cfg(target_env = "gnu")] const RLIMIT_CORE: c_uint = 4; -// PR_SET_DUMPABLE makes it so no other running process (root or same user) can dump the memory of this process -// or attach a debugger to it. +// PR_SET_DUMPABLE makes it so no other running process (root or same user) can dump the memory of +// this process or attach a debugger to it. // https://github.com/torvalds/linux/blob/a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6/include/uapi/linux/prctl.h#L14 const PR_SET_DUMPABLE: c_int = 4; diff --git a/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs b/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs index 3ff8a6d3d83..8d8e10d92c4 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs @@ -29,8 +29,9 @@ impl SecureMemoryStore for DpapiSecretKVStore { fn put(&mut self, key: String, value: &[u8]) { let length_header_len = std::mem::size_of::<usize>(); - // The allocated data has to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE, so we pad it and write the length in front - // We are storing LENGTH|DATA|00..00, where LENGTH is the length of DATA, the total length is a multiple + // The allocated data has to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE, so we pad it + // and write the length in front We are storing LENGTH|DATA|00..00, where LENGTH is + // the length of DATA, the total length is a multiple // of CRYPTPROTECTMEMORY_BLOCK_SIZE, and the padding is filled with zeros. let data_len = value.len(); diff --git a/apps/desktop/desktop_native/core/src/secure_memory/encrypted_memory_store.rs b/apps/desktop/desktop_native/core/src/secure_memory/encrypted_memory_store.rs index a8952d8f55a..d116e564bc8 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/encrypted_memory_store.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/encrypted_memory_store.rs @@ -10,8 +10,8 @@ use crate::secure_memory::{ /// allows circumventing length and amount limitations on platform specific secure memory APIs since /// only a single short item needs to be protected. /// -/// The key is briefly in process memory during encryption and decryption, in memory that is protected -/// from swapping to disk via mlock, and then zeroed out immediately after use. +/// The key is briefly in process memory during encryption and decryption, in memory that is +/// protected from swapping to disk via mlock, and then zeroed out immediately after use. #[allow(unused)] pub(crate) struct EncryptedMemoryStore { map: std::collections::HashMap<String, EncryptedMemory>, diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/crypto.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/crypto.rs index 1ee6c4cdf40..7e2917ade6d 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/crypto.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/crypto.rs @@ -6,9 +6,9 @@ use rand::{rng, Rng}; pub(super) const KEY_SIZE: usize = 32; pub(super) const NONCE_SIZE: usize = 24; -/// The encryption performed here is xchacha-poly1305. Any tampering with the key or the ciphertexts will result -/// in a decryption failure and panic. The key's memory contents are protected from being swapped to disk -/// via mlock. +/// The encryption performed here is xchacha-poly1305. Any tampering with the key or the ciphertexts +/// will result in a decryption failure and panic. The key's memory contents are protected from +/// being swapped to disk via mlock. pub(super) struct MemoryEncryptionKey(NonNull<[u8]>); /// An encrypted memory blob that must be decrypted using the same key that it was encrypted with. diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/dpapi.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/dpapi.rs index 0975b542877..52b75d94a09 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/dpapi.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/dpapi.rs @@ -1,10 +1,13 @@ -use super::crypto::{MemoryEncryptionKey, KEY_SIZE}; -use super::SecureKeyContainer; use windows::Win32::Security::Cryptography::{ CryptProtectMemory, CryptUnprotectMemory, CRYPTPROTECTMEMORY_BLOCK_SIZE, CRYPTPROTECTMEMORY_SAME_PROCESS, }; +use super::{ + crypto::{MemoryEncryptionKey, KEY_SIZE}, + SecureKeyContainer, +}; + /// https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata /// The DPAPI store encrypts data using the Windows Data Protection API (DPAPI). The key is bound /// to the current process, and cannot be decrypted by other user-mode processes. diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/keyctl.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/keyctl.rs index a738d964671..29c62759740 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/keyctl.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/keyctl.rs @@ -1,9 +1,8 @@ -use crate::secure_memory::secure_key::crypto::MemoryEncryptionKey; - -use super::crypto::KEY_SIZE; -use super::SecureKeyContainer; use linux_keyutils::{KeyRing, KeyRingIdentifier}; +use super::{crypto::KEY_SIZE, SecureKeyContainer}; +use crate::secure_memory::secure_key::crypto::MemoryEncryptionKey; + /// The keys are bound to the process keyring. const KEY_RING_IDENTIFIER: KeyRingIdentifier = KeyRingIdentifier::Process; /// This is an atomic global counter used to help generate unique key IDs @@ -26,9 +25,9 @@ pub(super) struct KeyctlSecureKeyContainer { id: String, } -// SAFETY: The key id is fully owned by this struct and not exposed or cloned, and cleaned up on drop. -// Further, since we use `KeyRingIdentifier::Process` and not `KeyRingIdentifier::Thread`, the key -// is accessible across threads within the same process bound. +// SAFETY: The key id is fully owned by this struct and not exposed or cloned, and cleaned up on +// drop. Further, since we use `KeyRingIdentifier::Process` and not `KeyRingIdentifier::Thread`, the +// key is accessible across threads within the same process bound. unsafe impl Send for KeyctlSecureKeyContainer {} // SAFETY: The container is non-mutable and thus safe to share between threads. unsafe impl Sync for KeyctlSecureKeyContainer {} diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/memfd_secret.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/memfd_secret.rs index 4e6a2c4d7ac..e9f96db3148 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/memfd_secret.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/memfd_secret.rs @@ -1,8 +1,9 @@ use std::{ptr::NonNull, sync::LazyLock}; -use super::crypto::MemoryEncryptionKey; -use super::crypto::KEY_SIZE; -use super::SecureKeyContainer; +use super::{ + crypto::{MemoryEncryptionKey, KEY_SIZE}, + SecureKeyContainer, +}; /// https://man.archlinux.org/man/memfd_secret.2.en /// The memfd_secret store protects the data using the `memfd_secret` syscall. The @@ -15,8 +16,8 @@ pub(super) struct MemfdSecretSecureKeyContainer { // SAFETY: The pointers in this struct are allocated by `memfd_secret`, and we have full ownership. // They are never exposed outside or cloned, and are cleaned up by drop. unsafe impl Send for MemfdSecretSecureKeyContainer {} -// SAFETY: The container is non-mutable and thus safe to share between threads. Further, memfd-secret -// is accessible across threads within the same process bound. +// SAFETY: The container is non-mutable and thus safe to share between threads. Further, +// memfd-secret is accessible across threads within the same process bound. unsafe impl Sync for MemfdSecretSecureKeyContainer {} impl SecureKeyContainer for MemfdSecretSecureKeyContainer { diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mlock.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mlock.rs index db21cd7fedc..961988c1d40 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mlock.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mlock.rs @@ -1,8 +1,9 @@ use std::ptr::NonNull; -use super::crypto::MemoryEncryptionKey; -use super::crypto::KEY_SIZE; -use super::SecureKeyContainer; +use super::{ + crypto::{MemoryEncryptionKey, KEY_SIZE}, + SecureKeyContainer, +}; /// A SecureKeyContainer that uses mlock to prevent the memory from being swapped to disk. /// This does not provide as strong protections as other methods, but is always supported. diff --git a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mod.rs b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mod.rs index 6c3b53117a5..26e72f7d581 100644 --- a/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mod.rs +++ b/apps/desktop/desktop_native/core/src/secure_memory/secure_key/mod.rs @@ -1,9 +1,12 @@ -//! This module provides hardened storage for single cryptographic keys. These are meant for encrypting large amounts of memory. -//! Some platforms restrict how many keys can be protected by their APIs, which necessitates this layer of indirection. This significantly -//! reduces the complexity of each platform specific implementation, since all that's needed is implementing protecting a single fixed sized key -//! instead of protecting many arbitrarily sized secrets. This significantly lowers the effort to maintain each implementation. +//! This module provides hardened storage for single cryptographic keys. These are meant for +//! encrypting large amounts of memory. Some platforms restrict how many keys can be protected by +//! their APIs, which necessitates this layer of indirection. This significantly reduces the +//! complexity of each platform specific implementation, since all that's needed is implementing +//! protecting a single fixed sized key instead of protecting many arbitrarily sized secrets. This +//! significantly lowers the effort to maintain each implementation. //! -//! The implementations include DPAPI on Windows, `keyctl` on Linux, and `memfd_secret` on Linux, and a fallback implementation using mlock. +//! The implementations include DPAPI on Windows, `keyctl` on Linux, and `memfd_secret` on Linux, +//! and a fallback implementation using mlock. use tracing::info; @@ -20,12 +23,13 @@ pub use crypto::EncryptedMemory; use crate::secure_memory::secure_key::crypto::DecryptionError; -/// An ephemeral key that is protected using a platform mechanism. It is generated on construction freshly, and can be used -/// to encrypt and decrypt segments of memory. Since the key is ephemeral, persistent data cannot be encrypted with this key. -/// On Linux and Windows, in most cases the protection mechanisms prevent memory dumps/debuggers from reading the key. +/// An ephemeral key that is protected using a platform mechanism. It is generated on construction +/// freshly, and can be used to encrypt and decrypt segments of memory. Since the key is ephemeral, +/// persistent data cannot be encrypted with this key. On Linux and Windows, in most cases the +/// protection mechanisms prevent memory dumps/debuggers from reading the key. /// -/// Note: This can be circumvented if code can be injected into the process and is only effective in combination with the -/// memory isolation provided in `process_isolation`. +/// Note: This can be circumvented if code can be injected into the process and is only effective in +/// combination with the memory isolation provided in `process_isolation`. /// - https://github.com/zer1t0/keydump #[allow(unused)] pub(crate) struct SecureMemoryEncryptionKey(CrossPlatformSecureKeyContainer); @@ -55,7 +59,8 @@ impl SecureMemoryEncryptionKey { /// from memory attacks. #[allow(unused)] trait SecureKeyContainer: Sync + Send { - /// Returns the key as a byte slice. This slice does not have additional memory protections applied. + /// Returns the key as a byte slice. This slice does not have additional memory protections + /// applied. fn as_key(&self) -> crypto::MemoryEncryptionKey; /// Creates a new SecureKeyContainer from the provided key. fn from_key(key: crypto::MemoryEncryptionKey) -> Self; diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs index 61cb8fc187d..8ba64618ffa 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/mod.rs @@ -7,13 +7,12 @@ use std::{ }; use base64::{engine::general_purpose::STANDARD, Engine as _}; -use tokio::sync::Mutex; -use tokio_util::sync::CancellationToken; - use bitwarden_russh::{ session_bind::SessionBindResult, ssh_agent::{self, SshKey}, }; +use tokio::sync::Mutex; +use tokio_util::sync::CancellationToken; use tracing::{error, info}; #[cfg_attr(target_os = "windows", path = "windows.rs")] @@ -34,7 +33,8 @@ pub struct BitwardenDesktopAgent { show_ui_request_tx: tokio::sync::mpsc::Sender<SshAgentUIRequest>, get_ui_response_rx: Arc<Mutex<tokio::sync::broadcast::Receiver<(u32, bool)>>>, request_id: Arc<AtomicU32>, - /// before first unlock, or after account switching, listing keys should require an unlock to get a list of public keys + /// before first unlock, or after account switching, listing keys should require an unlock to + /// get a list of public keys needs_unlock: Arc<AtomicBool>, is_running: Arc<AtomicBool>, } diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs index cb10e873a33..38b2193faf5 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/named_pipe_listener_stream.rs @@ -1,7 +1,6 @@ -use futures::Stream; -use std::os::windows::prelude::AsRawHandle as _; use std::{ io, + os::windows::prelude::AsRawHandle as _, pin::Pin, sync::{ atomic::{AtomicBool, Ordering}, @@ -9,6 +8,8 @@ use std::{ }, task::{Context, Poll}, }; + +use futures::Stream; use tokio::{ net::windows::named_pipe::{NamedPipeServer, ServerOptions}, select, diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs index 77eec5e35c7..5b6b1d8f36b 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peercred_unix_listener_stream.rs @@ -1,11 +1,13 @@ +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; + use futures::Stream; -use std::io; -use std::pin::Pin; -use std::task::{Context, Poll}; use tokio::net::{UnixListener, UnixStream}; -use super::peerinfo; -use super::peerinfo::models::PeerInfo; +use super::{peerinfo, peerinfo::models::PeerInfo}; #[derive(Debug)] pub struct PeercredUnixListenerStream { diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs index fad535cb80e..74b909f5ce7 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs @@ -1,9 +1,10 @@ use std::sync::{atomic::AtomicBool, Arc, Mutex}; /** -* Peerinfo represents the information of a peer process connecting over a socket. -* This can be later extended to include more information (icon, app name) for the corresponding application. -*/ + * Peerinfo represents the information of a peer process connecting over a socket. + * This can be later extended to include more information (icon, app name) for the corresponding + * application. + */ #[derive(Debug, Clone)] pub struct PeerInfo { uid: u32, diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs index a45c2f6c0bf..8623df13776 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/unix.rs @@ -6,9 +6,8 @@ use homedir::my_home; use tokio::{net::UnixListener, sync::Mutex}; use tracing::{error, info}; -use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; - use super::{BitwardenDesktopAgent, SshAgentUIRequest}; +use crate::ssh_agent::peercred_unix_listener_stream::PeercredUnixListenerStream; /// User can override the default socket path with this env var const ENV_BITWARDEN_SSH_AUTH_SOCK: &str = "BITWARDEN_SSH_AUTH_SOCK"; diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs index 662a4658ede..2012dab2d77 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/windows.rs @@ -2,6 +2,7 @@ use bitwarden_russh::ssh_agent; pub mod named_pipe_listener_stream; use std::sync::Arc; + use tokio::sync::Mutex; use super::{BitwardenDesktopAgent, SshAgentUIRequest}; diff --git a/apps/desktop/desktop_native/macos_provider/Cargo.toml b/apps/desktop/desktop_native/macos_provider/Cargo.toml index ea44f3d9a27..50f1834851d 100644 --- a/apps/desktop/desktop_native/macos_provider/Cargo.toml +++ b/apps/desktop/desktop_native/macos_provider/Cargo.toml @@ -14,17 +14,16 @@ crate-type = ["staticlib", "cdylib"] bench = false [dependencies] +uniffi = { workspace = true, features = ["cli"] } + +[target.'cfg(target_os = "macos")'.dependencies] desktop_core = { path = "../core" } futures = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true, features = ["sync"] } -tokio-util = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } -uniffi = { workspace = true, features = ["cli"] } - -[target.'cfg(target_os = "macos")'.dependencies] tracing-oslog = "0.3.0" [build-dependencies] diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 4198baa4b5a..b5847a602d5 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -16,17 +16,13 @@ manual_test = [] [dependencies] anyhow = { workspace = true } autotype = { path = "../autotype" } -base64 = { workspace = true } chromium_importer = { path = "../chromium_importer" } desktop_core = { path = "../core" } -hex = { workspace = true } napi = { workspace = true, features = ["async"] } napi-derive = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tokio = { workspace = true } -tokio-stream = { workspace = true } -tokio-util = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 0a8beb8c427..01bfa65d571 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -11,7 +11,10 @@ export declare namespace passwords { * Throws {@link Error} with message {@link PASSWORD_NOT_FOUND} if the password does not exist. */ export function getPassword(service: string, account: string): Promise<string> - /** Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. */ + /** + * Save the password to the keychain. Adds an entry if none exists otherwise updates the + * existing entry. + */ export function setPassword(service: string, account: string, password: string): Promise<void> /** * Delete the stored password from the keychain. @@ -35,7 +38,8 @@ export declare namespace biometrics { * base64 encoded key and the base64 encoded challenge used to create it * separated by a `|` character. * - * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated. + * If the iv is provided, it will be used as the challenge. Otherwise a random challenge will + * be generated. * * `format!("<key_base64>|<iv_base64>")` */ @@ -119,8 +123,9 @@ export declare namespace ipc { /** * Create and start the IPC server without blocking. * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - * @param callback This function will be called whenever a message is received from a client. + * @param name The endpoint name to listen on. This name uniquely identifies the IPC + * connection and must be the same for both the server and client. @param callback + * This function will be called whenever a message is received from a client. */ static listen(name: string, callback: (error: null | Error, message: IpcMessage) => void): Promise<IpcServer> /** Return the path to the IPC server. */ @@ -130,8 +135,9 @@ export declare namespace ipc { /** * Send a message over the IPC server to all the connected clients * - * @return The number of clients that the message was sent to. Note that the number of messages - * actually received may be less, as some clients could disconnect before receiving the message. + * @return The number of clients that the message was sent to. Note that the number of + * messages actually received may be less, as some clients could disconnect before + * receiving the message. */ send(message: string): number } @@ -194,8 +200,9 @@ export declare namespace autofill { /** * Create and start the IPC server without blocking. * - * @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - * @param callback This function will be called whenever a message is received from a client. + * @param name The endpoint name to listen on. This name uniquely identifies the IPC + * connection and must be the same for both the server and client. @param callback + * This function will be called whenever a message is received from a client. */ static listen(name: string, registrationCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyRegistrationRequest) => void, assertionCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionRequest) => void, assertionWithoutUserInterfaceCallback: (error: null | Error, clientId: number, sequenceNumber: number, message: PasskeyAssertionWithoutUserInterfaceRequest) => void): Promise<IpcServer> /** Return the path to the IPC server. */ diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 01d60ff5f56..c34e7574f68 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -19,7 +19,8 @@ pub mod passwords { .map_err(|e| napi::Error::from_reason(e.to_string())) } - /// Save the password to the keychain. Adds an entry if none exists otherwise updates the existing entry. + /// Save the password to the keychain. Adds an entry if none exists otherwise updates the + /// existing entry. #[napi] pub async fn set_password( service: String, @@ -107,7 +108,8 @@ pub mod biometrics { /// base64 encoded key and the base64 encoded challenge used to create it /// separated by a `|` character. /// - /// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will be generated. + /// If the iv is provided, it will be used as the challenge. Otherwise a random challenge will + /// be generated. /// /// `format!("<key_base64>|<iv_base64>")` #[allow(clippy::unused_async)] // FIXME: Remove unused async! @@ -556,8 +558,9 @@ pub mod ipc { impl IpcServer { /// Create and start the IPC server without blocking. /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// @param callback This function will be called whenever a message is received from a client. + /// @param name The endpoint name to listen on. This name uniquely identifies the IPC + /// connection and must be the same for both the server and client. @param callback + /// This function will be called whenever a message is received from a client. #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi(factory)] pub async fn listen( @@ -598,8 +601,9 @@ pub mod ipc { /// Send a message over the IPC server to all the connected clients /// - /// @return The number of clients that the message was sent to. Note that the number of messages - /// actually received may be less, as some clients could disconnect before receiving the message. + /// @return The number of clients that the message was sent to. Note that the number of + /// messages actually received may be less, as some clients could disconnect before + /// receiving the message. #[napi] pub fn send(&self, message: String) -> napi::Result<u32> { self.server @@ -743,8 +747,9 @@ pub mod autofill { impl IpcServer { /// Create and start the IPC server without blocking. /// - /// @param name The endpoint name to listen on. This name uniquely identifies the IPC connection and must be the same for both the server and client. - /// @param callback This function will be called whenever a message is received from a client. + /// @param name The endpoint name to listen on. This name uniquely identifies the IPC + /// connection and must be the same for both the server and client. @param callback + /// This function will be called whenever a message is received from a client. #[allow(clippy::unused_async)] // FIXME: Remove unused async! #[napi(factory)] pub async fn listen( @@ -946,18 +951,21 @@ pub mod logging { //! //! # Example //! - //! [Elec] 14:34:03.517 › [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock} + //! [Elec] 14:34:03.517 › [NAPI] [INFO] desktop_core::ssh_agent::platform_ssh_agent: Starting + //! SSH Agent server {socket=/Users/foo/.bitwarden-ssh-agent.sock} - use std::fmt::Write; - use std::sync::OnceLock; + use std::{fmt::Write, sync::OnceLock}; use napi::threadsafe_function::{ ErrorStrategy::CalleeHandled, ThreadsafeFunction, ThreadsafeFunctionCallMode, }; use tracing::Level; - use tracing_subscriber::fmt::format::{DefaultVisitor, Writer}; use tracing_subscriber::{ - filter::EnvFilter, layer::SubscriberExt, util::SubscriberInitExt, Layer, + filter::EnvFilter, + fmt::format::{DefaultVisitor, Writer}, + layer::SubscriberExt, + util::SubscriberInitExt, + Layer, }; struct JsLogger(OnceLock<ThreadsafeFunction<(LogLevel, String), CalleeHandled>>); @@ -1069,6 +1077,8 @@ pub mod logging { #[napi] pub mod chromium_importer { + use std::collections::HashMap; + use chromium_importer::{ chromium::{ DefaultInstalledBrowserRetriever, LoginImportResult as _LoginImportResult, @@ -1076,7 +1086,6 @@ pub mod chromium_importer { }, metadata::NativeImporterMetadata as _NativeImporterMetadata, }; - use std::collections::HashMap; #[napi(object)] pub struct ProfileInfo { diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index fc8910bddd3..c161b8226ba 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -8,16 +8,12 @@ publish = { workspace = true } [features] default = [] -[dependencies] +[target.'cfg(target_os = "macos")'.dependencies] anyhow = { workspace = true } -thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } -[target.'cfg(target_os = "macos")'.dependencies] -core-foundation = "=0.10.1" - -[build-dependencies] +[target.'cfg(target_os = "macos")'.build-dependencies] cc = "=1.2.4" glob = "=0.3.2" diff --git a/apps/desktop/desktop_native/process_isolation/Cargo.toml b/apps/desktop/desktop_native/process_isolation/Cargo.toml index 170832c2fde..d8c6c7a618c 100644 --- a/apps/desktop/desktop_native/process_isolation/Cargo.toml +++ b/apps/desktop/desktop_native/process_isolation/Cargo.toml @@ -8,7 +8,7 @@ publish = { workspace = true } [lib] crate-type = ["cdylib"] -[dependencies] +[target.'cfg(target_os = "linux")'.dependencies] ctor = { workspace = true } desktop_core = { path = "../core" } libc = { workspace = true } diff --git a/apps/desktop/desktop_native/process_isolation/src/lib.rs b/apps/desktop/desktop_native/process_isolation/src/lib.rs index 850ffac841e..55c5d7fafae 100644 --- a/apps/desktop/desktop_native/process_isolation/src/lib.rs +++ b/apps/desktop/desktop_native/process_isolation/src/lib.rs @@ -5,8 +5,9 @@ //! On Linux, this is PR_SET_DUMPABLE to prevent debuggers from attaching, the env //! from being read and the memory from being stolen. -use desktop_core::process_isolation; use std::{ffi::c_char, sync::LazyLock}; + +use desktop_core::process_isolation; use tracing::info; static ORIGINAL_UNSETENV: LazyLock<unsafe extern "C" fn(*const c_char) -> i32> = diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml index c672f57543d..25682fe2aa3 100644 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ b/apps/desktop/desktop_native/proxy/Cargo.toml @@ -6,7 +6,6 @@ version = { workspace = true } publish = { workspace = true } [dependencies] -anyhow = { workspace = true } desktop_core = { path = "../core" } futures = { workspace = true } tokio = { workspace = true, features = ["io-std", "io-util", "macros", "rt"] } diff --git a/apps/desktop/desktop_native/proxy/src/main.rs b/apps/desktop/desktop_native/proxy/src/main.rs index c2c525b865a..21957d8ba32 100644 --- a/apps/desktop/desktop_native/proxy/src/main.rs +++ b/apps/desktop/desktop_native/proxy/src/main.rs @@ -60,7 +60,6 @@ fn init_logging(log_path: &Path, console_level: LevelFilter, file_level: LevelFi /// a stable communication channel between the proxy and the running desktop application. /// /// Browser extension <-[native messaging]-> proxy <-[ipc]-> desktop -/// // FIXME: Remove unwraps! They panic and terminate the whole application. #[allow(clippy::unwrap_used)] #[tokio::main(flavor = "current_thread")] @@ -83,8 +82,10 @@ async fn main() { // Different browsers send different arguments when the app starts: // // Firefox: - // - The complete path to the app manifest. (in the form `/Users/<user>/Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`) - // - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in the form `{[UUID]}`). + // - The complete path to the app manifest. (in the form + // `/Users/<user>/Library/.../Mozilla/NativeMessagingHosts/com.8bit.bitwarden.json`) + // - (in Firefox 55+) the ID (as given in the manifest.json) of the add-on that started it (in + // the form `{[UUID]}`). // // Chrome on Windows: // - Origin of the extension that started it (in the form `chrome-extension://[ID]`). @@ -96,7 +97,8 @@ async fn main() { let args: Vec<_> = std::env::args().skip(1).collect(); info!(?args, "Process args"); - // Setup two channels, one for sending messages to the desktop application (`out`) and one for receiving messages from the desktop application (`in`) + // Setup two channels, one for sending messages to the desktop application (`out`) and one for + // receiving messages from the desktop application (`in`) let (in_send, in_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); let (out_send, mut out_recv) = tokio::sync::mpsc::channel(MESSAGE_CHANNEL_BUFFER); diff --git a/apps/desktop/desktop_native/rustfmt.toml b/apps/desktop/desktop_native/rustfmt.toml new file mode 100644 index 00000000000..bb3baeccd76 --- /dev/null +++ b/apps/desktop/desktop_native/rustfmt.toml @@ -0,0 +1,7 @@ +# Wrap comments and increase the width of comments to 100 +comment_width = 100 +wrap_comments = true + +# Sort and group imports +group_imports = "StdExternalCrate" +imports_granularity = "Crate" diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs index 2e4f453d8f0..893fdf765fc 100644 --- a/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/lib.rs @@ -2,11 +2,12 @@ #![allow(non_snake_case)] #![allow(non_camel_case_types)] -use std::ffi::c_uchar; -use std::ptr; -use windows::Win32::Foundation::*; -use windows::Win32::System::Com::*; -use windows::Win32::System::LibraryLoader::*; +use std::{ffi::c_uchar, ptr}; + +use windows::Win32::{ + Foundation::*, + System::{Com::*, LibraryLoader::*}, +}; use windows_core::*; mod pluginauthenticator; From 0912d1abe8f280be48f6c28cb932d6407bb22341 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:31:02 +0100 Subject: [PATCH 179/249] Autosync the updated translations (#17462) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 72 ++++++------- apps/web/src/locales/ar/messages.json | 72 ++++++------- apps/web/src/locales/az/messages.json | 82 +++++++-------- apps/web/src/locales/be/messages.json | 72 ++++++------- apps/web/src/locales/bg/messages.json | 78 +++++++------- apps/web/src/locales/bn/messages.json | 72 ++++++------- apps/web/src/locales/bs/messages.json | 72 ++++++------- apps/web/src/locales/ca/messages.json | 72 ++++++------- apps/web/src/locales/cs/messages.json | 70 ++++++------- apps/web/src/locales/cy/messages.json | 72 ++++++------- apps/web/src/locales/da/messages.json | 72 ++++++------- apps/web/src/locales/de/messages.json | 84 +++++++-------- apps/web/src/locales/el/messages.json | 72 ++++++------- apps/web/src/locales/en_GB/messages.json | 72 ++++++------- apps/web/src/locales/en_IN/messages.json | 72 ++++++------- apps/web/src/locales/eo/messages.json | 72 ++++++------- apps/web/src/locales/es/messages.json | 72 ++++++------- apps/web/src/locales/et/messages.json | 72 ++++++------- apps/web/src/locales/eu/messages.json | 72 ++++++------- apps/web/src/locales/fa/messages.json | 72 ++++++------- apps/web/src/locales/fi/messages.json | 72 ++++++------- apps/web/src/locales/fil/messages.json | 72 ++++++------- apps/web/src/locales/fr/messages.json | 78 +++++++------- apps/web/src/locales/gl/messages.json | 72 ++++++------- apps/web/src/locales/he/messages.json | 128 +++++++++++------------ apps/web/src/locales/hi/messages.json | 72 ++++++------- apps/web/src/locales/hr/messages.json | 74 ++++++------- apps/web/src/locales/hu/messages.json | 74 ++++++------- apps/web/src/locales/id/messages.json | 74 ++++++------- apps/web/src/locales/it/messages.json | 72 ++++++------- apps/web/src/locales/ja/messages.json | 72 ++++++------- apps/web/src/locales/ka/messages.json | 72 ++++++------- apps/web/src/locales/km/messages.json | 72 ++++++------- apps/web/src/locales/kn/messages.json | 72 ++++++------- apps/web/src/locales/ko/messages.json | 72 ++++++------- apps/web/src/locales/lv/messages.json | 74 ++++++------- apps/web/src/locales/ml/messages.json | 72 ++++++------- apps/web/src/locales/mr/messages.json | 72 ++++++------- apps/web/src/locales/my/messages.json | 72 ++++++------- apps/web/src/locales/nb/messages.json | 72 ++++++------- apps/web/src/locales/ne/messages.json | 72 ++++++------- apps/web/src/locales/nl/messages.json | 74 ++++++------- apps/web/src/locales/nn/messages.json | 72 ++++++------- apps/web/src/locales/or/messages.json | 72 ++++++------- apps/web/src/locales/pl/messages.json | 72 ++++++------- apps/web/src/locales/pt_BR/messages.json | 72 ++++++------- apps/web/src/locales/pt_PT/messages.json | 76 +++++++------- apps/web/src/locales/ro/messages.json | 72 ++++++------- apps/web/src/locales/ru/messages.json | 72 ++++++------- apps/web/src/locales/si/messages.json | 72 ++++++------- apps/web/src/locales/sk/messages.json | 102 +++++++++--------- apps/web/src/locales/sl/messages.json | 72 ++++++------- apps/web/src/locales/sr_CS/messages.json | 72 ++++++------- apps/web/src/locales/sr_CY/messages.json | 74 ++++++------- apps/web/src/locales/sv/messages.json | 80 +++++++------- apps/web/src/locales/ta/messages.json | 72 ++++++------- apps/web/src/locales/te/messages.json | 72 ++++++------- apps/web/src/locales/th/messages.json | 72 ++++++------- apps/web/src/locales/tr/messages.json | 74 ++++++------- apps/web/src/locales/uk/messages.json | 72 ++++++------- apps/web/src/locales/vi/messages.json | 74 ++++++------- apps/web/src/locales/zh_CN/messages.json | 104 +++++++++--------- apps/web/src/locales/zh_TW/messages.json | 78 +++++++------- 63 files changed, 2171 insertions(+), 2549 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 684e107cd57..2fee7410e93 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index fb42b2b5fa2..7dbfa670741 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "الأعضاء المعرضون للخطر" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "يقوم هؤلاء الأعضاء بتسجيل الدخول إلى التطبيقات باستخدام كلمات مرور ضعيفة أو مكشوفة أو مُعادة الاستخدام." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "يقوم هؤلاء الأعضاء بتسجيل الدخول إلى التطبيقات باستخدام كلمات مرور ضعيفة أو مكشوفة أو مُعادة الاستخدام." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 8dcfa862f03..9ff3fc46040 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "İrəliləyişi izləmək üçün üzvlərə tapşırıqlar təyin edin" }, - "onceYouReviewApps": { - "message": "Tətbiqləri incələyib kritik olaraq işarələdikdən sonra, riskli elementləri həll etməsi üçün üzvlərə tapşırıqlar verə və irəliləyişi burada izləyə bilərsiniz" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Xatırlatma göndər" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "$ORG NAME$ üçün heç bir tətbiq tapılmadı", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Kimlik məlumatı təhlükəsizlik risklərini monitorinq etməyə başlamaq üçün təşkilatınızın giriş verilərini daxilə köçürün. Daxilə köçürdükdən sonra əldə edəcəkləriniz:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Riskləri prioritetləşdirmə" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Ən vacib tətbiqlərə fokuslanma" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Riskləri azaltma bələdçisi" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Risk altındakı üzvlərə, riskli kimlik məlumatlarını dəyişmək üçün rəhbər tapşırıqlar təyin edin" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "İrəliləyişin monitorinqi" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Təhlükəsizlik təkmilləşdirmələrini göstərmək üçün zamanla dəyişiklikləri izləyin" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Tətbiqləri görmək üçün ilk hesabatınızı çalışdırın" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Təşkilatınızın tətbiqlərini təhlil etmək və diqqət edilməli riskli parolları müəyyənləşdirmək üçün risk təhlili hesabatını yaradın. İlk hesabatınızı çalışdırdıqda:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Heç bir tətbiqi kritik olaraq işarələməmisiniz" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Riskli üzvlər" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Kritik tətbiqlər üçün risk altındakı elementlərə erişimi olan üzvlər" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Riskli parollara sahib üzvlər" }, - "membersWillReceiveNotification": { - "message": "Üzvlər, riskli girişləri həll etmək üçün brauzer uzantısı üzərindən bildiriş alacaqlar." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ üzv risk altındadır", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Bu üzvlər, tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edən üzv yoxdur." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Kritik tətbiqləri prioritetləşdir" }, - "selectCriticalApplicationsDescription": { - "message": "Təşkilatınız üçün ən kritik sayılan tətbiqləri seçin, daha sonra riskləri həll etmələri üçün üzvlərə təhlükəsizlik tapşırıqları təyin edin." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Yeni tətbiqlər incələ" }, - "reviewNewApplicationsDescription": { - "message": "Admin konsolunda saxlanılmış və zəif, ifşa olunmuş və ya təkrar istifadə olunmuş parollara sahib yeni tətbiqlər üçün riskli elementləri vurğulamışıq." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Bir tətbiqi kritik olaraq işarələmək üçün ulduz ikonuna klikləyin" @@ -3251,7 +3245,7 @@ "message": "Növbəti ödəniş" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Növbəti ödəniş" }, "plan": { "message": "Plan" @@ -3260,7 +3254,7 @@ "message": "Təfsilatlar" }, "discount": { - "message": "discount" + "message": "endirim" }, "downloadLicense": { "message": "Lisenziyanı endir" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Tapşırıq təyin et" }, - "assignTasksToMembers": { - "message": "Üzvlərə rəhbər həll üçün tapşırıqlar təyin edin" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Kolleksiyalara təyin et" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Heç bir kritik tətbiq seçilməyib" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Davam etmək istədiyinizə əminsiniz?" } } diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index 9fd22d0ccd0..e562c39ea7f 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Удзельнікі ў зоне рызыкі" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Гэтыя ўдзельнікі ўваходзяць у праграму з ненадзейнымі, скампраметаванымі або паўторна выкарыстанымі паролямі." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 6f17d4b264b..1f897b5d74e 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Назначете задачи на членовете, за да следите напредъка" }, - "onceYouReviewApps": { - "message": "След като прегледате приложенията и отбележите някои като важни, можете да зададете задачи на членовете, така че те да се занимаят с нещата в риск, а Вие да следите напредъка тук" + "onceYouReviewApplications": { + "message": "След като прегледате приложенията и ги отбележите като важни, можете да зададете задачи на членовете си, за да сменят паролите си." }, "sendReminders": { "message": "Изпращане на напомняния" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Няма намерени приложения за $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Няма намерени данни" }, - "noApplicationsInOrgDescription": { - "message": "Внесете данните за вписване на организацията си, за да започнете да следите рисковете по сигурността им. След внасянето ще можете:" + "noDataInOrgDescription": { + "message": "Внесете данните за вписване на организацията си, за да започнете да използвате Анализа на достъпа. След като направите това, ще можете да:" }, - "benefit1Title": { - "message": "Да приоритизирате рисковете" + "feature1Title": { + "message": "Отбелязвате приложения като важни" }, - "benefit1Description": { - "message": "Фокусирайте се върху приложенията, които са най-важни" + "feature1Description": { + "message": "Това ще Ви помогне да отстранявате рисковете първо в най-важните си приложения." }, - "benefit2Title": { - "message": "Да давате насоки за подобряване" + "feature2Title": { + "message": "Помагате на членовете да подобряват сигурността си" }, - "benefit2Description": { - "message": "Давайте задачи на членовете в риск, така че те да не забравят да променят данните си" + "feature2Description": { + "message": "Задавайте на членовете в риск задачи по сигурността, които да ги насочват в промяната на данните им." }, - "benefit3Title": { - "message": "Да следите напредъка" + "feature3Title": { + "message": "Следите напредъка" }, - "benefit3Description": { - "message": "Следете промените във времето, за да виждате как сигурността се подобрява" + "feature3Description": { + "message": "Следете промените във времето, за да виждате как сигурността се подобрява." }, - "noReportRunTitle": { - "message": "Създайте първия си доклад, за да видите приложенията" + "noReportsRunTitle": { + "message": "Създаване на доклад" }, - "noReportRunDescription": { - "message": "Създайте доклад с подробности за рисковете, за да анализирате приложенията в организацията си и да установите кои пароли са в риск и имат нужда от внимание. Създаването на първия доклад ще:" + "noReportsRunDescription": { + "message": "Всичко е готово и можете да започнете да създавате доклади. След като го направите, ще можете да:" }, "noCriticalApplicationsTitle": { "message": "Не сте отбелязали нито едно приложение като важно" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Членове в риск" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Членове с достъп до елементи в риск за важни приложения" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "Тези членове имат достъп до рисковите елементи за важните приложения." }, "membersWithAtRiskPasswords": { "message": "Членове с пароли в риск" }, - "membersWillReceiveNotification": { - "message": "Членовете ще получат известие, за да отстранят проблема с данните си за вписване в риск, чрез добавката за браузъра." + "membersWillReceiveSecurityTask": { + "message": "Членовете на организацията ще получат задача за промяна на рисковите пароли. Те ще получат известие в разширението за браузър на Битуорден." }, "membersAtRiskCount": { "message": "$COUNT$ членове в риск", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Тези членове се вписват в приложенията със слаби, преизползвани или разобличени пароли." + "atRiskMemberDescription": { + "message": "Тези членове се вписват във важните приложения със слаби, преизползвани или разкрити пароли." }, "atRiskMembersDescriptionNone": { "message": "Няма членове, които се вписват в приложенията със слаби, преизползвани или разобличени пароли." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Даване на приоритет на важните приложения" }, - "selectCriticalApplicationsDescription": { - "message": "Изберете кои приложения са най-важни за организацията Ви, а след това раздайте задачи на членовете, за да отстраните рисковете." + "selectCriticalAppsDescription": { + "message": "Изберете кои приложения са най-важни за организацията Ви. След това ще можете да зададете задачи на членовете, за да отстраните рисковете." }, "reviewNewApplications": { "message": "Преглед на новите приложения" }, - "reviewNewApplicationsDescription": { - "message": "Отбелязахме елементите в риск за новите приложения съхранявани в Административната конзола, които имат слаби, преизползвани или разкрити пароли." + "reviewNewAppsDescription": { + "message": "Прегледайте новите приложения с рискови елементи и ги отбележете, ако искате да ги следите внимателно като важни. След това ще можете да задавате задачи на членовете, за да отстраните рисковете." }, "clickIconToMarkAppAsCritical": { "message": "Щракнете върху иконката със звезда, за да отбележите приложение като важно" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Назначаване на задачи" }, - "assignTasksToMembers": { - "message": "Задавайте задачи на членовете, за да отстраняват проблемите" + "assignSecurityTasksToMembers": { + "message": "Изпращане на известия за промяна на пароли" }, "assignToCollections": { "message": "Свързване с колекции" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Няма избрани важни приложения" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Наистина ли искате да продължите?" } } diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 4baf480b517..67fddc7d6e0 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index f9b5481cd23..cae2a248d0a 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 2994137855e..77204835dbe 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assigna a col·leccions" diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index ed82a38c390..ccb2172dbf6 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Přiřadit úkoly členů ke sledování průběhu" }, - "onceYouReviewApps": { - "message": "Jakmile zkontrolujete aplikace a označíte je jako kritické, můžete přidělit členům úkoly k řešení ohrožených položek a sledovat průběh zde" + "onceYouReviewApplications": { + "message": "Jakmile zkontrolujete aplikace a označíte je jako kritické, přiřaďte svým členům úkoly ke změně jejich hesel." }, "sendReminders": { "message": "Odeslat připomenutí" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Pro $ORG NAME$ nebyly nalezeny žádné aplikace", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Nebyla nalezena žádná data" }, - "noApplicationsInOrgDescription": { - "message": "Importujte přihlašovací údaje Vaší organizace a začněte sledovat bezpečnostní rizika spojená s přihlašovacími údaji. Po importu získáte přístup k:" + "noDataInOrgDescription": { + "message": "Importujte přihlašovací údaje Vaší organizace a začněte s Access Intelligence. Jakmile to uděláte, budete moci:" }, - "benefit1Title": { - "message": "Upřednostnit rizika" + "feature1Title": { + "message": "Označit aplikace jako kritické" }, - "benefit1Description": { - "message": "Zaměřit se na aplikace, na které záleží nejvíce" + "feature1Description": { + "message": "To Vám pomůže nejprve odebrat rizika pro Vaše nejdůležitější aplikace." }, - "benefit2Title": { - "message": "Průvodce nápravou" + "feature2Title": { + "message": "Pomoci členům zlepšit jejich zabezpečení" }, - "benefit2Description": { - "message": "Přiřadit ohroženým členům řízené úkoly, aby střídali používání ohrožených přihlašovacích údajů" + "feature2Description": { + "message": "Přiřaďte ohroženým členům řízené bezpečnostní úkoly k aktualizaci přihlašovacích údajů." }, - "benefit3Title": { + "feature3Title": { "message": "Sledovat průběh" }, - "benefit3Description": { + "feature3Description": { "message": "Sleduje změny v průběhu času pro zobrazení zlepšení zabezpečení." }, - "noReportRunTitle": { - "message": "Spustit první hlášení pro zobrazení aplikací" + "noReportsRunTitle": { + "message": "Vygenerovat přehled" }, - "noReportRunDescription": { - "message": "Vygenerujte zprávu o rizicích, abyste mohli analyzovat aplikace Vaší organizace a identifikovat riziková hesla, která vyžadují pozornost. Spuštěním první zprávy:" + "noReportsRunDescription": { + "message": "Jste připraveni začít generovat přehledy. Jakmile je vytvoříte, budete moci:" }, "noCriticalApplicationsTitle": { "message": "Neoznačili jste žádné aplikace jako kritické" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Ohrožení členové" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Členové s přístupem k rizikovým položkám pro kritické aplikace" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "Tito členové mají přístup k zranitelným položkám pro kritické aplikace." }, "membersWithAtRiskPasswords": { "message": "Členové s ohroženými hesly" }, - "membersWillReceiveNotification": { - "message": "Členové obdrží oznámení, aby vyřešili ohrožená přihlášení prostřednictvím rozšíření prohlížeče." + "membersWillReceiveSecurityTask": { + "message": "Členům Vaší organizace bude přiřazen úkol ke změně zranitelných hesel. Obdrželi oznámení v rámci jejich rozšíření prohlížeče Bitwarden." }, "membersAtRiskCount": { "message": "$COUNT$ členů v ohrožení", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Tito členové se přihlašují do aplikací se slabými, odhalenými nebo znovu použitými hesly." + "atRiskMemberDescription": { + "message": "Tito členové se přihlašují do kritických aplikací s odhalenými nebo znovu použitými hesly." }, "atRiskMembersDescriptionNone": { "message": "Žádní členové se nepřihlašují do aplikací se slabými, odhalenými nebo znovu použitými hesly." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Upřednostnit kritické aplikace" }, - "selectCriticalApplicationsDescription": { - "message": "Vybere, které aplikace jsou pro Vaši organizaci nejkritičtější, a pro řešení rizik přiřaďte členům bezpečnostní úkoly." + "selectCriticalAppsDescription": { + "message": "Vyberte, které aplikace jsou pro Vaši organizaci nejkritičtější. Potom budete moci přidělit členům bezpečnostní úkoly pro odebrání rizik." }, "reviewNewApplications": { "message": "Zkontrolovat nové aplikace" }, - "reviewNewApplicationsDescription": { - "message": "Zvýraznili jsme rizikové položky pro nové aplikace uložené v administrátorské konzoli, které mají slabá, exponovaná nebo znovu použitá hesla." + "reviewNewAppsDescription": { + "message": "Zkontrolujte nové aplikace s citlivými položkami a označte ty, které chcete pečlivě sledovat jako kritické. Potom budete moci přidělit členům bezpečnostní úkoly, abyste odebrali rizika." }, "clickIconToMarkAppAsCritical": { "message": "Klepnutím na ikonu hvězdičky označte aplikaci jako kritickou" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Přiřadit úkoly" }, - "assignTasksToMembers": { - "message": "Přiřadit úkoly členům k řízenému řešení" + "assignSecurityTasksToMembers": { + "message": "Odeslat oznámení pro změnu hesla" }, "assignToCollections": { "message": "Přiřadit ke sbírkám" diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index 8c62a789fbd..c140204619e 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index 7ddd60a2b75..cc2a58aa27d 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Udsatte medlemmer" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Disse medlemmer logger ind på programmer med svage, udsatte eller genbrugte adgangskoder." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Tildel til samlinger" diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 162eefe9923..073126da446 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Mitglieder Aufgaben zuweisen, um den Fortschritt zu überwachen" }, - "onceYouReviewApps": { - "message": "Sobald du die Anwendungen überprüft und als kritisch markiert hast, kannst du Mitgliedern Aufgaben zuweisen, um gefährdete Einträge zu beheben und den Fortschritt hier überwachen" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Erinnerungen senden" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Keine Anwendungen für $ORG NAME$ gefunden", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Importiere die Zugangsdaten deiner Organisation, um mit der Überwachung von Sicherheitsrisiken für Zugangsdaten zu starten. Nach dem Import kannst du:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Risiken priorisieren" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Auf Anwendungen konzentrieren, die am wichtigsten sind" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Anleitung zur Behebung" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Gefährdeten Mitgliedern gezielte Aufgaben zuweisen, um gefährdete Zugangsdaten zu erneuern" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Fortschritt überwachen" }, - "benefit3Description": { - "message": "Änderungen im Laufe der Zeit verfolgen, um Sicherheitsverbesserungen anzuzeigen" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Erstelle deinen ersten Bericht, um Anwendungen zu sehen" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Erstelle einen Risikoanalysebericht, um die Anwendungen deines Unternehmens zu analysieren und gefährdete Passwörter zu identifizieren, die Aufmerksamkeit erfordern. Die Erstellung deines ersten Berichts wird:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Du hast keine Anwendung als kritisch markiert" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Gefährdete Mitglieder" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Mitglieder mit Zugriff auf gefährdete Einträge für kritische Anwendungen" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Mitglieder mit gefährdeten Passwörtern" }, - "membersWillReceiveNotification": { - "message": "Mitglieder erhalten über die Browser-Erweiterung eine Benachrichtigung, um gefährdete Zugangsdaten zu ändern." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ gefährdete Mitglieder", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Diese Mitglieder melden sich bei Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern an." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Dies sind keine Mitglieder, die sich in Anwendungen mit schwachen, kompromittierten oder wiederverwendeten Passwörtern anmelden." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Kritische Anwendungen priorisieren" }, - "selectCriticalApplicationsDescription": { - "message": "Wähle die für deine Organisation kritischsten Anwendungen aus und weise den Mitgliedern Sicherheitsaufgaben zu, um Risiken zu beseitigen." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Neue Anwendungen überprüfen" }, - "reviewNewApplicationsDescription": { - "message": "Wir haben gefährdete Einträge für neue Anwendungen hervorgehoben, die in der Administrator-Konsole gespeichert sind und schwache, kompromittierte oder wiederverwendete Passwörter beinhalten." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Klicke auf das Sternsymbol, um eine App als kritisch zu markieren" @@ -3251,16 +3245,16 @@ "message": "Nächste Abbuchung" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Nächste Abbuchung" }, "plan": { - "message": "Plan" + "message": "Tarif" }, "details": { "message": "Details" }, "discount": { - "message": "discount" + "message": "Rabatt" }, "downloadLicense": { "message": "Lizenz herunterladen" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Aufgaben zuweisen" }, - "assignTasksToMembers": { - "message": "Mitgliedern Aufgaben für eine gezielte Lösung zuweisen" + "assignSecurityTasksToMembers": { + "message": "Sende Benachrichtigungen, um Passwörter zu ändern" }, "assignToCollections": { "message": "Sammlungen zuweisen" @@ -10369,7 +10363,7 @@ "message": "Index" }, "selectAPlan": { - "message": "Ein Abo auswählen" + "message": "Einen Tarif auswählen" }, "thirtyFivePercentDiscount": { "message": "35% Rabatt" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Es sind keine kritischen Anwendungen ausgewählt" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Bist du sicher, dass du fortfahren möchtest?" } } diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index fb066120434..606d10fa600 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Ανάθεση σε συλλογές" diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 2270d95056c..2f40b6fa13d 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organisation's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritise risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyse your organisation's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organisation, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 99ebcf22413..180b90cf878 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organisation's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritise risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyse your organisation's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritise critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organisation, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index b1acc78487d..94fd92f45c9 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index 80767d520e8..be77b075923 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Asignar a colecciones" diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 0e47bce83ff..2dddfda1bb6 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 95281e381ca..785ba6e2e0d 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index aec55daad51..71da2a1b8ba 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "اعضای در معرض خطر" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "این اعضا با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ها می‌شوند." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "هیچ عضوی با کلمات عبور ضعیف، افشا شده یا تکراری وارد برنامه‌ها نمی‌شود." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "اختصاص به مجموعه‌ها" diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index 8a1c7550d42..b735280c312 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Riskialttiit jäsenet" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Määritä kokoelmiin" diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index 6b0d5e3e8a5..f91cf0d9bf0 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index 8d51d685d79..d97fca90d57 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Affecter des tâches aux membres pour surveiller la progression" }, - "onceYouReviewApps": { - "message": "Une fois que vous examinez les applications et marquez comme critiques, vous pouvez assigner des tâches aux membres pour résoudre les éléments à risque et suivre les progrès ici" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Envoyer des rappels" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Aucune application trouvée pour $ORG NOM$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Importez les données de connexion de votre organisation pour commencer à surveiller les risques de sécurité des identifiants. Une fois importé, vous pouvez :" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioriser les risques" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Vous concentrer sur les applications qui comptent le plus" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guider la remédiation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assignez aux membres à risque des tâches guidées pour faire pivoter les identifiants à risque." + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Suivre la progression" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Suivre les changements au fil du temps pour afficher les améliorations de sécurité" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Exécutez votre premier rapport pour voir les applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Générer un rapport de connaissance des risques pour analyser les applications de votre organisation et identifier les mots de passe à risque qui nécessitent une attention. Exécuter votre premier rapport fera :" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Vous n'avez marqué aucune application comme critique" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Membres à risque" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Les membres ayant accès aux éléments à risque pour les applications critiques" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Membres avec des mots de passe à risque" }, - "membersWillReceiveNotification": { - "message": "Les membres recevront une notification pour résoudre les identifiants à risque via l'extension du navigateur." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ membres à risque", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ces membres se connectent à des applications avec des mots de passe faibles, exposés ou réutilisés." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Aucun membre ne se connecte dans des applications avec des mots de passe faibles, exposés ou réutilisés." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioriser les applications critiques" }, - "selectCriticalApplicationsDescription": { - "message": "Sélectionnez les applications les plus critiques pour votre organisation, puis attribuez des tâches de sécurité aux membres pour résoudre les risques." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Examiner les nouvelles applications" }, - "reviewNewApplicationsDescription": { - "message": "Nous avons mis en évidence des éléments à risque pour les nouvelles applications stockées dans la console Admin qui ont des mots de passe faibles, exposés ou réutilisés." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Cliquez sur l'icône étoile pour marquer une application comme critique" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assigner des tâches" }, - "assignTasksToMembers": { - "message": "Assigner des tâches aux membres pour une résolution guidée" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assigner aux collections" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Aucune application critique n'est sélectionnée" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Êtes-vous sûr de vouloir continuer ?" } } diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index e5c8ce9b49f..c9fc10749d7 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index b60e76f6a75..5eb5c7969ee 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -21,13 +21,13 @@ "message": "סיכון סיסמה" }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "אין לך הרשאות לערוך את הפריט הזה" }, "reviewAtRiskPasswords": { "message": "סקור סיסמאות בסיכון (חלשות, חשופות, או משומשות) בין יישומים. בחר את היישומים הכי קריטיים שלך על מנת לתעדף פעולות אבטחה עבור המשתמשים שלך כדי לטפל בסיסמאות בסיכון." }, "reviewAtRiskLoginsPrompt": { - "message": "Review at-risk logins" + "message": "סקור כניסות בסיכון" }, "dataLastUpdated": { "message": "הנתונים עודכנו לאחרונה: $DATE$", @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "הקצה משימות לחברים כדי לנטר התקדמות" }, - "onceYouReviewApps": { - "message": "לאחר שתסקור יישומים ותסמן אותם כקריטיים, באפשרותך להקצות משימות לחברים כדי לפתור פריטים בסיכון ולנטר התקדמות כאן" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "שלח תזכורות" @@ -131,7 +131,7 @@ } }, "criticalApplicationsMarked": { - "message": "critical applications marked" + "message": "יישומים קריטיים סומנו" }, "countOfCriticalApplications": { "message": "$COUNT$ יישומים קריטיים", @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "לא נמצאו יישומים עבור $ORG NAME", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "ייבא את נתוני הכניסה של הארגון שלך כדי להתחיל בניטור סיכוני אבטחה של אישורים. לאחר הייבוא תוכל:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "לתעדף סיכונים" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "התמקדות ביישומים החשובים ביותר" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "להדריך תיקון" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "הקצה משימות מודרכות לחברים בסיכון כדי להחליף אישורים בסיכון" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "לנטר התקדמות" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "עקוב אחר שינויים לאורך זמן כדי להציג שיפורי אבטחה" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "הרץ את הדוח הראשון שלך כדי לראות יישומים" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "ייצר דוח תובנות סיכון כדי לנתח את היישומים של הארגון שלך ולזהות סיסמאות בסיכון שצריכות תשומת לב. הרצת הדוח הראשון שלך תגרום ל:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "לא סימנת אף יישום כקריטי" @@ -242,7 +236,7 @@ "message": "יישומים המסומנים כקריטיים" }, "criticalApplicationsMarkedSuccess": { - "message": "$COUNT$ applications marked as critical", + "message": "$COUNT$ יישומים סומנו כקריטיים", "placeholders": { "count": { "content": "$1", @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "חברים בסיכון" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "חברים עם גישה לפריטים בסיכון עבור יישומים קריטיים" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { - "message": "Members with at-risk passwords" + "message": "חברים עם סיסמאות בסיכון" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ חברים בסיכון", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "חברים אלה נכנסו אל יישומים עם סיסמאות חלשות, חשופות, או משומשות." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "אין חברים שנכנסו אל יישומים עם סיסמאות חלשות, חשופות, או משומשות." @@ -350,7 +344,7 @@ "message": "יישומים צריכים סקירה" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "סקור יישומים חדשים" }, "newApplicationsWithCount": { "message": "$COUNT$ יישומים חדשים", @@ -368,7 +362,7 @@ "message": "סקור עכשיו" }, "allCaughtUp": { - "message": "All caught up!" + "message": "הכל טופל!" }, "noNewApplicationsToReviewAtThisTime": { "message": "No new applications to review at this time" @@ -386,19 +380,19 @@ "message": "Review applications to secure the items most critical to your organization's security" }, "reviewApplications": { - "message": "Review applications" + "message": "סקור יישומים" }, "prioritizeCriticalApplications": { "message": "תעדוף יישומים קריטיים" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { - "message": "Review new applications" + "message": "סקור יישומים חדשים" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -881,7 +875,7 @@ "message": "מועדפים" }, "taskSummary": { - "message": "Task summary" + "message": "סיכום משימה" }, "types": { "message": "סוגים" @@ -3251,16 +3245,16 @@ "message": "החיוב הבא" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "החיוב הבא" }, "plan": { - "message": "Plan" + "message": "תוכנית" }, "details": { "message": "פרטים" }, "discount": { - "message": "discount" + "message": "הנחה" }, "downloadLicense": { "message": "הורד רישיון" @@ -5856,14 +5850,14 @@ "message": "איך להפעיל אישור משתמש אוטומטי" }, "autoConfirmExtension1": { - "message": "Open your Bitwarden extension" + "message": "פתח את הרחבת Bitwarden שלך" }, "autoConfirmExtension2": { - "message": "Select", + "message": "בחר", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtension3": { - "message": " Turn on", + "message": " הפעל", "description": "This is a fragment of a larger sencence. The whole sentence will read: 'Select Turn on'" }, "autoConfirmExtensionOpened": { @@ -5954,13 +5948,13 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "uriMatchDetectionPolicy": { - "message": "Default URI match detection" + "message": "ברירת מחדל לזיהוי התאמת URI" }, "uriMatchDetectionPolicyDesc": { "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." }, "uriMatchDetectionOptionsLabel": { - "message": "Default URI match detection" + "message": "ברירת מחדל לזיהוי התאמת URI" }, "invalidUriMatchDefaultPolicySetting": { "message": "Please select a valid URI match detection option.", @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "הקצה משימות" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "הקצה לאוספים" @@ -12125,28 +12119,28 @@ "message": "התחל ניסיון משפחות בחינם" }, "unlockMethodNeededToChangeTimeoutActionDesc": { - "message": "Set up an unlock method to change your vault timeout action." + "message": "הגדר שיטת ביטול נעילה כדי לשנות את פעולת פסק הזמן לכספת שלך." }, "vaultTimeoutPolicyAffectingOptions": { - "message": "Enterprise policy requirements have been applied to your timeout options" + "message": "דרישות מדיניות ארגונית הוחלו על אפשרויות פסק הזמן שלך" }, "vaultTimeoutTooLarge": { - "message": "Your vault timeout exceeds the restrictions set by your organization." + "message": "פסק הזמן לכספת שלך חורג מהמגבלות שנקבעו על ידי הארגון שלך." }, "neverLockWarning": { - "message": "Are you sure you want to use the \"Never\" option? Setting your lock options to \"Never\" stores your vault's encryption key on your device. If you use this option you should ensure that you keep your device properly protected." + "message": "האם אתה בטוח שברצונך להשתמש באפשרות \"אף פעם לא\"? במצב זה הסיסמה לכספת שלך תשמר על המכשיר שלך. אם תשתמש באפשרות זו עליך לוודא כי המכשיר מאובטח כראוי." }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "פעולת פסק זמן" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "פסק זמן להפעלה" }, "appearance": { - "message": "Appearance" + "message": "מראה" }, "vaultTimeoutPolicyMaximumError": { - "message": "Timeout exceeds the restriction set by your organization: $HOURS$ hour(s) and $MINUTES$ minute(s) maximum", + "message": "פסק זמן חורג את ההגבלה שהוגדרה על ידי הארגון שלך: $HOURS$ שעות ו־$MINUTES$ דקות לכל היותר", "placeholders": { "hours": { "content": "$1", @@ -12159,7 +12153,7 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "לא סומנו יישומים קריטים" }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 01cf40c3c0d..766c6c3e8bd 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index e0f0ce0b28b..a0dbd290ab3 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Dodijeli članovima zadatke za praćenje napretka" }, - "onceYouReviewApps": { - "message": "Nakon što pregledaš prijave i označiš kritične, možeš dodijeliti zadatke članovima za rješavanje rizičnih stavki i ovdje pratiti napredak" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Pošalji podsjetnike" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Nisu nađene aplikacije u $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Uvezi podatke za prijavu svoje organizacije za početak praćenja sigurnosnih rizika vjerodajnica. Nakon uvoza možeš:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritizirajte rizike" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Usredotoči se na najvažnije aplikacije" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Vodič za sanaciju" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Dodijeli vođene zadatke rizičnim članovima za rotaciju rizičnih vjerodajnica" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Prati napredak" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Prati promjene tijekom vremena za prikaz sigurnosnih poboljšanja" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Za pregled aplikacija, pokreni svoje prvo izvješće" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generiraj izvješće o uvidima u rizike za analizu aplikacije svoje organizacije i identifikaciju lozinki koje su u riziku i kojima je potrebna pozornost. Pokretanje tvojeg prvog izvješća će:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Niti jedna aplikacija nije označena kao kritična" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Rizični korisnici" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Članovi koji imaju pristup stavkama za aplikacije označene kao kritične" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Članovi s rizičnim lozinkama" }, - "membersWillReceiveNotification": { - "message": "Članovi će dobiti obavijest o rješavanju rizičnih prijava putem proširenja preglednika." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "Rizičnih članova: $COUNT$", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ovi članovi se prijavljuju u aplikacije slabim, izloženim ili ponovno korištenim lozinkama." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Nema članova koji se prijavljuju slabim, izloženim ili ponovno korištenim lozinkama." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Daj prioritet kritičnim aplikacijama" }, - "selectCriticalApplicationsDescription": { - "message": "Odaberi koje su aplikacije najvažnije za tvoju organizaciju, a zatim dodijeli sigurnosne zadatke članovima kako bi se riješili rizici." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Pregledaj nove prijave" }, - "reviewNewApplicationsDescription": { - "message": "Istaknuli smo rizične stavke za nove aplikacije pohranjene u administratorskoj konzoli koje imaju slabe, otkrivene ili ponovno korištene lozinke." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Klikni ikonu zvjezdice za označavanje aplikacije kao kritične" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Dodijeli zadatke" }, - "assignTasksToMembers": { - "message": "Dodijeli zadatke članovima za vođeno rješavanje" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Dodijeli zbirkama" diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index d9bbd09b0f6..df292a29c02 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Rendeljünk a tagokhoz feladatokat az előrehaladás monitorozására." }, - "onceYouReviewApps": { - "message": "Miután az alkalmazásokat áttekintettük és kritikusként jelöltük meg azokat, feladatokat rendelhetünk a tagokhoz a kockázatos elemek megoldására és itt nyomon követhetjük a feldolgozást." + "onceYouReviewApplications": { + "message": "Az alkalmazások áttekintése és azok kritikusként megjelölése után rendeljünk feladatokat a tagoknak a jelszavak megváltoztatásához." }, "sendReminders": { "message": "Emlékeztető küldés" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Nem találhatók alkalmazások: $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Nem található adat." }, - "noApplicationsInOrgDescription": { - "message": "A szervezet bejelentkezési adatainak importálása a hitelesítő adatok biztonsági kockázat figyelésének megkezdéséhez. Az importálás után a következőkhöz jutunk el:" + "noDataInOrgDescription": { + "message": "Importáljuk a szervezet bejelentkezési adatait az Access Intelligence használatának megkezdéséhez. Ezután a következőket tehetjük:" }, - "benefit1Title": { - "message": "Kockázatok rangsorolása" + "feature1Title": { + "message": "Alkalmazások megjelölése kritikusként" }, - "benefit1Description": { - "message": "Összpontosítás a legfontosabb alkalmazásokra" + "feature1Description": { + "message": "Ez először is segít eltávolítani a legfontosabb alkalmazások kockázatait." }, - "benefit2Title": { - "message": "Kármentési útmutató" + "feature2Title": { + "message": "Segítsünk a tagoknak biztonságuk javításában." }, - "benefit2Description": { - "message": "Irányított feladatok hozzárendelése a veszélyeztetett tagokhoz a veszélyeztetett jogosultságok rotálásához." + "feature2Description": { + "message": "Rendeljünk a veszélyeztetett tagoknak irányított biztonsági feladatokat a hitelesítő adatok frissítéséhez." }, - "benefit3Title": { - "message": "Folyamatok nyomonkövetése" + "feature3Title": { + "message": "Folyamat nyomonkövetése" }, - "benefit3Description": { - "message": "Kövessük az idő múlásával bekövetkező változásokat a biztonsági fejlesztések megjelenítéséhez." + "feature3Description": { + "message": "Kövessük nyomon az időbeli változásokat a biztonsági fejlesztések megjelenítéséhez." }, - "noReportRunTitle": { - "message": "Futtassuk le az első jelentést az alkalmazások megtekintéséhez" + "noReportsRunTitle": { + "message": "Jelentés generálása" }, - "noReportRunDescription": { - "message": "Készítsünk kockázati betekintés jelentést a szervezet alkalmazásainak elemzéséhez és azonosítsuk azokat a veszélyeztetett jelszavakat, amelyekre figyelmet kell fordítani. Az első jelentés futtatása:" + "noReportsRunDescription": { + "message": "Készen állunk a jelentések generálására. A generálás után a következőket teheti meg:" }, "noCriticalApplicationsTitle": { "message": "Egyetlen alkalmazás sem lett megjelölve kritikusként." @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Veszélyes tagok" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "Ezek a tagok hozzáférhetnek a kritikus alkalmazásoknál a sebezhető elemekhez." }, "membersWithAtRiskPasswords": { "message": "Kockázatos jelszavú tagok" }, - "membersWillReceiveNotification": { - "message": "A tagok értesítést kapnak a kockázatos bejelentkezések megoldásáról a böngésző bővítményen keresztül." + "membersWillReceiveSecurityTask": { + "message": "A szervezet tagjaira feladat hárul a sebezhető jelszavak megváltoztatására. Értesítést kapnak a Bitwarden böngésző bővítményen belül." }, "membersAtRiskCount": { "message": "$COUNT$ kockázatos tag", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ezek a tagok gyenge, nyílt vagy újrafelhasznált jelszavakkal jelentkeznek be az alkalmazásokba." + "atRiskMemberDescription": { + "message": "Ezek a tagok gyenge, kitett vagy újrafelhasznált jelszavakkal jelentkeznek be az alkalmazásokba." }, "atRiskMembersDescriptionNone": { "message": "Ezek nem olyan tagok, akik gyenge, kitett vagy újrafelhasznált jelszavakkal jelentkeznek be az alkalmazásokba." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Kritikus alkalmazások rangsorolása" }, - "selectCriticalApplicationsDescription": { - "message": "Válasszuk ki, hogy mely alkalmazások a legkritikusabbak a szervezet számára, majd rendeljünk biztonsági feladatokat a tagokhoz a kockázatok megoldása érdekében." + "selectCriticalAppsDescription": { + "message": "Válasszuk ki, hogy mely alkalmazások a legkritikusabbak a szervezet számára. Ezután rendeljünk biztonsági feladatokat a tagokhoz a kockázatok eltávolítása érdekében." }, "reviewNewApplications": { "message": "Új alkalmazások felülvizsgálata" }, - "reviewNewApplicationsDescription": { - "message": "Kiemelésre került az Admin konzolban tárolt új alkalmazások veszélyeztetett elemei, amelyek gyenge, kitett vagy újrafelhasznált jelszavakkal rendelkeznek." + "reviewNewAppsDescription": { + "message": "Tekintsük át a sebezhető elemeket tartalmazó új alkalmazásokat és jelöljük meg kritikusnak azokat, amelyeket szorosan figyelemmel szeretnénk kísérni. Ezután biztonsági feladatokat rendelhetünk a tagokhoz a kockázatok eltávolítása érdekében." }, "clickIconToMarkAppAsCritical": { "message": "Kattintsunk a csillag ikonra egy alkalmazás kritikusként megjelöléséhez." @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Feladatok hozzárendelése" }, - "assignTasksToMembers": { - "message": "Feladatok hozzárendelése a tagokhoz irányított megoldáshoz." + "assignSecurityTasksToMembers": { + "message": "Értesítések küldése a jelszavak megváltoztatásához" }, "assignToCollections": { "message": "Hozzárendelés gyűjteményekhez" diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index ad9d2405adb..388ca3ba9cf 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Tetapkan tugas ke anggota untuk memantau progres" }, - "onceYouReviewApps": { - "message": "Setelah meninjau aplikasi dan menandainya sebagai penting, Anda dapat menetapkan tugas kepada anggota untuk menyelesaikan item berisiko dan memantau kemajuan di sini" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Kirim pengingat" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Tidak ada aplikasi ditemukan untuk $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Impor data login organisasi Anda untuk mulai memantau risiko keamanan kredensial. Setelah diimpor, Anda akan mendapatkan:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritaskan risiko" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Fokus pada aplikasi yang paling penting" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Memantau kemajuan" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Lacak perubahan dari waktu ke waktu untuk menunjukkan peningkatan keamanan" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Jalankan laporan pertama Anda untuk melihat aplikasi" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Anda belum menandai aplikasi apa pun sebagai penting" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Anggota berisiko" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Anggota dengan akses ke item berisiko untuk apl penting" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ anggota berisiko", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Anggota ini masuk ke aplikasi dengan sandi yang lemah, terekspos, atau sama." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Anggota ini masuk ke aplikasi dengan sandi yang lemah, terekspos, atau sama." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritaskan aplikasi penting" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index fa3146996d7..b60e8f4584a 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assegna compiti ai membri per monitorarne i progressi" }, - "onceYouReviewApps": { - "message": "Dopo aver esaminato le applicazioni e contrassegnato quelle critiche, puoi assegnare attività ai membri per risolvere gli elementi a rischio e monitorare qui lo stato di avanzamento" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Invia promemoria" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Nessuna applicazione trovata per $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Importa i dati di accesso della tua organizzazione per iniziare a monitorare i rischi per la sicurezza delle credenziali. Una volta importati, potrai:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Priorità dei rischi" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Concentrati sulle applicazioni più importanti" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Non hai contrassegnato nessuna applicazione come critica" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Membri a rischio" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Membri con accesso agli elementi a rischio per applicazioni critiche" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ membri a rischio", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Questi membri accedono ad applicazioni con password deboli, esposte, o riutilizzate." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Non ci sono utenti connessi con password deboli, esposte o riutilizzate." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Priorità alle applicazioni critiche" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assegna attività" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assegna alle raccolte" diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index a70b821a1fc..eed4085320d 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "リスクがあるメンバー" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "これらのメンバーは、脆弱な、または流出したか再利用されたパスワードでアプリにログインしています。" + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "コレクションに割り当てる" diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index 973dcc2951b..b64d882bb03 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 47df4826851..9f57ed8c6ca 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 17aaa51f261..41017933852 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index f831e76b1eb..971a2ee59ae 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index b35e704eb21..ba978730477 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Piešķirt dalībniekiem uzdevumus, lai pārraudzītu virzību" }, - "onceYouReviewApps": { - "message": "Tiklīdz lietotnes būs pārskatītas un atzīmētas kā būtiskas, dalībniekiem varēs piešķirt uzdevumus atrisināt riskam pakļautos vienumus un šeit pārskatīt virzību" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Nosūtīt atgādinājumus" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "$ORG NAME$ netika atrasta neviena lietotne", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Ievieto savas apvienības pieteikšanās datus, lai uzsāktu to drošības risku uzraudzīšanu. Tiklīdz dati ir ievietoti, Tu vari:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Noteikt risku svarīgumu" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Pievērst uzmanību visbūtiskākajām lietotnēm" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Virzīt nepilnību novēršanu" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Piešķirt riskam pakļautajiem dalībniekiem uzdevumus riskam pakļauto pieteikšanās datu nomaiņai" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Pārraudzīt virzību" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Sekot izmaiņām, lai uzrādītu drošības uzlabojumus" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Izveidot savu pirmo atskaiti, lai redzētu lietotnes" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Izveidot riska ieskatu atskaiti, lai izvērtētu savas apvienības lietotnes un noteiktu riskam pakļautas paroles, kurēm jāpievērš uzmanība. Pirmās atskaites izveidošana:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Neviena lietotne nav atzīmēta kā kritiska" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Riskam pakļautie dalībnieki" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Dalībnieki ar piekluvi riskam pakļautajiem vienumiem būtiskajām lietotnēm" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Dalībnieki ar riskam pakļautām parolēm" }, - "membersWillReceiveNotification": { - "message": "Dalībnieki pārlūka paplašinājumā saņems paziņojumu, ka jānomaina riskam pakļautās paroles." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ dalībnieki ir pakļauti riskam", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Šie dalībnieki piesakās lietotnēs ar vājām, atklātām vai atkārtoti izmantotām parolēm." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Nav dalībnieku, kas piesakās lietotnēs ar vājām, atklātām vai atkārtoti izmantotām parolēm." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Paaugstināt būtisko lietotņu svarīgumu" }, - "selectCriticalApplicationsDescription": { - "message": "Jāatlasa, kuras lietontes apvienībai ir visbūtiskākās, tad jāpiešķir drošības uzdevumi dalībniekiem risku novēršanai." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Pārskatīt jaunās lietotnes" }, - "reviewNewApplicationsDescription": { - "message": "Mēs izcēlām riskam pakļautos vienumus jaunām lietotnēm, kas tiek glabātas pārvaldības konsolē un kurās ir vājas, atklātas vai atkārtoti izmantotas paroles." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Jāklikšķina uz zvaigznes, lai atzīmētu lietotni kā būtisku" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Piešķirt uzdevumus" }, - "assignTasksToMembers": { - "message": "Piešķirt uzdevumus dalībniekiem risinājumam ar norādēm" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Piešķirt krājumiem" diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 624121be5d9..14f5b921ce7 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index 612ff8b8765..a7a1e405ed7 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 47df4826851..9f57ed8c6ca 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index b2fb413be8c..345d875875c 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Legg til i samlinger" diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 8bd88ef4a57..8387c568e94 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 8ac8cf24ca5..35d61fc7ce3 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Leden taken toewijzen om de voortgang te controleren" }, - "onceYouReviewApps": { - "message": "Zodra je applicaties beoordeelt en ze als kritiek markeert, kun je leden taken toewijzen om risico-items op te lossen en de voortgang hier te controleren" + "onceYouReviewApplications": { + "message": "Zodra je applicaties beoordeelt en als belangrijk markeert, wijs je taken toe aan je leden om hun wachtwoorden te wijzigen." }, "sendReminders": { "message": "Herinneringen versturen" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Geen gegevens gevonden" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Importeer de inloggegevens van je organisatie om aan de slag te gaan met Access Intelligence. Zodra je dat doet, kun je:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Applicaties als belangrijk markeren" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "Hiermee kun je eerst risico's voor je belangrijkste applicaties weghalen." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help leden hun beveiliging te verbeteren" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Risicovolle leden begeleide beveiligingstaken toewijzien om inloggegevens bij te werken." }, - "benefit3Title": { - "message": "Monitor progress" + "feature3Title": { + "message": "Voortgang monitoren" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Bijhouden van wijzigingen over tijd voor het weergeven van beveiligingsverbeteringen." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Rapport genereren" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "Je bent klaar om rapporten te genereren. Zodra je rapporten maakt, kun je:" }, "noCriticalApplicationsTitle": { "message": "Je hebt nog geen applicaties als belangrijk aangewezen" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Leden in gevaar" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Leden met toegang tot risico-items voor belangrijke applicaties" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "Deze leden hebben toegang tot kwetsbare items voor belangrijke applicaties." }, "membersWithAtRiskPasswords": { "message": "Leden met risicovolle wachtwoorden" }, - "membersWillReceiveNotification": { - "message": "Leden ontvangen een melding via de browserextensie om risico-logins op te lossen." + "membersWillReceiveSecurityTask": { + "message": "Leden van jew organisatie krijgen een taak om kwetsbare wachtwoorden te wijzigen. Ze ontvangen een melding binnen hun Bitwarden-browserextensie." }, "membersAtRiskCount": { "message": "$COUNT$ leden lopen risico", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Deze leden loggen in op toepassingen met zwakke, blootgestelde of hergebruikte wachtwoorden." + "atRiskMemberDescription": { + "message": "Deze leden loggen in op applicaties met zwakke, blootgestelde of hergebruikte wachtwoorden." }, "atRiskMembersDescriptionNone": { "message": "Deze niet-leden loggen in op toepassingen met zwakke, blootgestelde of hergebruikte wachtwoorden." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Belangrijke applicaties prioriteren" }, - "selectCriticalApplicationsDescription": { - "message": "Selecteer welke toepassingen het meest belangrijk zijn voor je organisatie en wijs de leden dan beveiligingstaken toe om risico's op te lossen." + "selectCriticalAppsDescription": { + "message": "Selecteer welke toepassingen het meest belangrijk zijn voor je organisatie. Vervolgens kun je leden beveiligingstaken toewijzen om risico's weg te halen." }, "reviewNewApplications": { "message": "Nieuwe toepassingen beoordelen" }, - "reviewNewApplicationsDescription": { - "message": "We hebben items met risico gemarkeerd voor nieuwe toepassingen die zijn opgeslagen in Admin-console die zwakke, onthulde of hergebruikte wachtwoorden hebben." + "reviewNewAppsDescription": { + "message": "Bekijk nieuwe applicaties met kwetsbare items en markeer degenen die je wilt monitoren als belangrijk. Vervolgens kun je beveiligingstaken toewijzen aan leden om risico's weg te halen." }, "clickIconToMarkAppAsCritical": { "message": "Klik op het sterpictogram om een app als belangrijk te markeren" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Taken toewijzen" }, - "assignTasksToMembers": { - "message": "Taken aan leden toewijzen voor een begeleide oplossing" + "assignSecurityTasksToMembers": { + "message": "Meldingen verzenden om wachtwoorden te wijzigen" }, "assignToCollections": { "message": "Toewijzen aan collecties" diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index b6ec66bf695..4979a3eb72a 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 47df4826851..9f57ed8c6ca 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 9750a2a7df7..48f25c451ae 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Zagrożeni użytkownicy" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ci członkowie logują się do aplikacji ze słabymi, ujawnionymi lub ponownie używanymi hasłami." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Ci członkowie nie logują się do aplikacji ze słabymi, ujawnionymi lub ponownie używanymi hasłami." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Przypisz do kolekcji" diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 4ef989dcd75..4520e190b53 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Atribuir tarefas a membros para monitorar progresso" }, - "onceYouReviewApps": { - "message": "Ao revisar aplicativos e marcá-los como críticos, você pode atribuir traferas a membros para resolver itens em risco e monitorar o progresso aqui" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Enviar lembretes" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Você não marcou nenhum aplicativo como crítico" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Membros em risco" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Membros com acesso a itens em risco de aplicativos críticos" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Esses membros estão conectando-se em aplicativos com senhas fracas, expostas, ou reutilizadas." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Não há nenhum membro se conectando em aplicativos com senhas fracas, expostas, ou reutilizadas." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Priorizar aplicativos críticos" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Atribuir à coleções" diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 45b511a857f..4831f14d694 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Atribuir tarefas aos membros para monitorizar o progresso" }, - "onceYouReviewApps": { - "message": "Depois de analisar as aplicações e marcá-las como críticas, pode atribuir tarefas aos membros para resolver itens em risco e monitorizar o progresso aqui" + "onceYouReviewApplications": { + "message": "Depois de rever as aplicações e marcá-las como críticas, atribua tarefas aos seus membros para que alterem as respetivas palavras-passe." }, "sendReminders": { "message": "Enviar lembretes" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Não foram encontradas aplicações para $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Não foram encontrados dados" }, - "noApplicationsInOrgDescription": { - "message": "Importe as credenciais da sua organização para começar a monitorizar os riscos de segurança das credenciais. Após a importação, poderá:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Priorizar os riscos" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Com foco nas aplicações mais importantes" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Remediação de guias" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Atribuir tarefas orientadas aos membros em risco para alterar as credenciais em risco" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Monitorizar o progresso" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Acompanhe as alterações ao longo do tempo para mostrar as melhorias na segurança" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Execute o seu primeiro relatório para ver as aplicações" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Gere um relatório de insights de risco para analisar as aplicações da sua organização e identificar palavras-passe em risco que precisam de atenção. A execução do seu primeiro relatório irá:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Não marcou nenhuma aplicação como crítica" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Membros em risco" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Membros com acesso a itens em risco para aplicações críticas" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Membros com palavras-passe em risco" }, - "membersWillReceiveNotification": { - "message": "Os membros receberão uma notificação para resolver as credenciais em risco através da extensão do navegador." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Estes membros estão a iniciar sessão em aplicações com palavras-passe fracas, expostas ou reutilizadas." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Estes não são membros que iniciam sessão em aplicações com palavras-passe fracas, expostas ou reutilizadas." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Dê prioridade a aplicações críticas" }, - "selectCriticalApplicationsDescription": { - "message": "Selecione quais aplicações são mais críticas para a sua organização e, em seguida, atribua tarefas de segurança aos membros para resolver os riscos." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Rever novas aplicações" }, - "reviewNewApplicationsDescription": { - "message": "Destacámos os itens em risco para novas aplicações armazenadas na consola de administração que têm palavras-passe fracas, expostas ou reutilizadas." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Clique no ícone de estrela para marcar uma app como crítica" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Atribuir tarefas" }, - "assignTasksToMembers": { - "message": "Atribuir tarefas aos membros para resolução guiada" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Atribuir às coleções" @@ -12083,7 +12077,7 @@ "message": "Tem o plano gratuito do Bitwarden" }, "upgradeCompleteSecurity": { - "message": "Atualize para segurança total" + "message": "Atualize para obter segurança total" }, "viewbusinessplans": { "message": "Ver planos empresariais" diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index e683e0a5f10..0c3eaa3ea08 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 9b5a9399491..205e953c0ed 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Назначайте членам команды задачи по мониторингу прогресса" }, - "onceYouReviewApps": { - "message": "Как только вы оцените приложения и пометите их как критичные, вы можете назначить задачи участникам касающиеся элементов, подверженных риску, и следить за прогрессом здесь" + "onceYouReviewApplications": { + "message": "После того как вы просмотрите приложения и отметите их как критичные, назначьте своим пользователям задачи по смене паролей." }, "sendReminders": { "message": "Отправить напоминания" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Не найдено приложений для $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Данные не найдены" }, - "noApplicationsInOrgDescription": { - "message": "Импортируйте логины вашей организации, чтобы начать мониторинг угроз безопасности учетных данных. После импорта вы получите доступ к:" + "noDataInOrgDescription": { + "message": "Импортируйте данные логинов вашей организации, чтобы начать работу с Access Intelligence. Как только вы это сделаете, вы сможете:" }, - "benefit1Title": { - "message": "Приоритет рисков" + "feature1Title": { + "message": "Пометить приложения как критичные" }, - "benefit1Description": { - "message": "Сосредоточьтесь на наиболее важных приложениях" + "feature1Description": { + "message": "Это поможет вам в первую очередь устранить риски для ваших наиболее важных приложений." }, - "benefit2Title": { - "message": "Руководство по восстановлению" + "feature2Title": { + "message": "Помогите пользователями повысить безопасность" }, - "benefit2Description": { - "message": "Назначайте участникам, подверженным риску, управляемые задания для ротации учетных данных" + "feature2Description": { + "message": "Назначайте участникам, находящимся в группе риска, управляемые задачи по обеспечению безопасности для обновления учетных данных." }, - "benefit3Title": { + "feature3Title": { "message": "Отслеживать прогресс" }, - "benefit3Description": { - "message": "Отслеживать изменения с течением времени, чтобы показать улучшения безопасности" + "feature3Description": { + "message": "Отслеживать изменения с течением времени, чтобы показать улучшения безопасности." }, - "noReportRunTitle": { - "message": "Запустите ваш первый отчет для просмотра приложений" + "noReportsRunTitle": { + "message": "Создать отчет" }, - "noReportRunDescription": { - "message": "Создайте аналитический отчет о рисках для анализа приложений вашей организации и выявления паролей, подверженных риску, которые требуют внимания. Запуск первого отчета позволит:" + "noReportsRunDescription": { + "message": "Вы готовы приступить к созданию отчетов. После создания вы сможете:" }, "noCriticalApplicationsTitle": { "message": "Вы не отметили ни одного приложения в качестве критичного" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Участники, подверженные риску" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Участники, имеющие доступ к элементам, подверженным риску, для критичных приложений" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "Эти пользователи имеют доступ к уязвимым элементам критичных приложений." }, "membersWithAtRiskPasswords": { "message": "Пользователи, пароли которых подверженны риску" }, - "membersWillReceiveNotification": { - "message": "Пользователи получат уведомление о разрешении подверженных риску логинов с помощью расширения браузера." + "membersWillReceiveSecurityTask": { + "message": "Сотрудникам вашей организации будет поручено изменить уязвимые пароли. Они получат уведомление в своем расширении Bitwarden для браузера." }, "membersAtRiskCount": { "message": "$COUNT$ участников, подверженных риску", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Эти пользователи входят в приложения со слабыми, скомпрометированными или повторно используемыми паролями." + "atRiskMemberDescription": { + "message": "Эти пользователи входят в критичные приложения со слабыми, скомпрометированными или повторно используемыми паролями." }, "atRiskMembersDescriptionNone": { "message": "Нет пользователей, входящих в приложения со слабыми, скомпрометированными или повторно используемыми паролями." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Приоритет критичных приложений" }, - "selectCriticalApplicationsDescription": { - "message": "Выберите, какие приложения наиболее критичны для вашей организации, а затем назначьте пользователям задачи по обеспечению безопасности для устранения рисков." + "selectCriticalAppsDescription": { + "message": "Выберите, какие приложения наиболее критичны для вашей организации. Затем вы сможете назначить пользователям задачи по обеспечению безопасности по устранению рисков." }, "reviewNewApplications": { "message": "Обзор новых приложений" }, - "reviewNewApplicationsDescription": { - "message": "Мы выделили элементы, подверженные риску, для новых приложений, хранящихся в консоли администратора, которые имеют слабые, незащищенные или повторно используемые пароли." + "reviewNewAppsDescription": { + "message": "Просмотрите новые приложения, содержащие уязвимые элементы, и отметьте те, за которыми вы хотели бы внимательно следить, как критичные. После этого вы сможете назначать пользователям задачи по обеспечению безопасности для устранения рисков." }, "clickIconToMarkAppAsCritical": { "message": "Нажмите на значок звездочки, чтобы отметить приложение как критичное" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Назначить задачи" }, - "assignTasksToMembers": { - "message": "Назначайте задачи пользователям для управляемого решения" + "assignSecurityTasksToMembers": { + "message": "Отправляйте уведомления о смене паролей" }, "assignToCollections": { "message": "Назначить коллекциям" diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index 0fdc35fb8dc..aeb1a4b4e0e 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index fdcaa412db9..30afb3afcc2 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Pre sledovanie progresu, priraďte členom úlohy" }, - "onceYouReviewApps": { - "message": "Keď skontrolujete a označíte kritické aplikácie, môžete na tomto mieste prideliť členom úlohy pre ohrozené položky a sledovať progres" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Poslať upomienky" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Neboli nájdené žiadne aplikácie v $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Pre začatie monitorovania bezpečnostných problémov importujte prihlasovacie údaje vašej organizácie. Po importe budete mať k dispozícii:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritizácia bezpečnostných problémov" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Zamerajte sa na najdôležitejšie aplikácie" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Sprievodca nápravou" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Prideľte ohrozeným členom úlohy pre obnovu ohrozených prihlasovacích údajov" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Monitorovanie progresu" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Zobrazte zlepšenie zabezpečenia sledovaním zmien v priebehu času" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Pre prehľad aplikácií generujte váš prvý report" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generujte prehľad rizík pre analýzu aplikácií vo vašej organizácii a identifikujte ohrozene heslá ktoré vyžadujú vašu pozornosť. Spustením vášho prvého reportu:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Neoznačili ste žiadne aplikácie ako kritické" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Ohrozených členov" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Členovia s prístupom k ohrozeným položkám kritických aplikácii" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Členovia s ohrozenými heslami" }, - "membersWillReceiveNotification": { - "message": "Členovia dostanú notifikáciu aby vyriešili problémy s ohrozeným heslom prostredníctvom rozšírenia pre prehliadače." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ ohrozených členov", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Títo členovia sa prihlasujú do aplikácií so slabým, uniknutým alebo viacnásobne použitým heslom." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -368,13 +362,13 @@ "message": "Skontrolovať teraz" }, "allCaughtUp": { - "message": "All caught up!" + "message": "Všetko hotové!" }, "noNewApplicationsToReviewAtThisTime": { - "message": "No new applications to review at this time" + "message": "Žiadne nové aplikácie na kontrolu" }, "organizationHasItemsSavedForApplications": { - "message": "Your organization has items saved for $COUNT$ applications", + "message": "Vaša organizácia ma uložené položky pre $COUNT$ aplikácii", "placeholders": { "count": { "content": "$1", @@ -383,22 +377,22 @@ } }, "reviewApplicationsToSecureItems": { - "message": "Review applications to secure the items most critical to your organization's security" + "message": "Pre zabezpečenie položiek najkritickejších pre bezpečnosť vašej organizácie, skontrolujte aplikácie" }, "reviewApplications": { - "message": "Review applications" + "message": "Skontrolovať aplikácie" }, "prioritizeCriticalApplications": { "message": "Uprednostniť kritické aplikácie" }, - "selectCriticalApplicationsDescription": { - "message": "Vyberte ktoré aplikácie sú pre vašu organizáciu najkritickejšie, potom prideľte členom bezpečnostné úlohy pre vyriešenie ohrozených hesiel." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Aplikáciu označíte za kritickú kliknutím na ikonu hviezdičky" @@ -3251,16 +3245,16 @@ "message": "Ďalšia platba" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Ďalšia platba" }, "plan": { - "message": "Plan" + "message": "Plán" }, "details": { "message": "Podrobnosti" }, "discount": { - "message": "discount" + "message": "zľava" }, "downloadLicense": { "message": "Stiahnuť licenciu" @@ -5954,16 +5948,16 @@ "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, "uriMatchDetectionPolicy": { - "message": "Default URI match detection" + "message": "Predvolené zisťovanie zhody URI" }, "uriMatchDetectionPolicyDesc": { - "message": "Determine when logins are suggested for autofill. Admins and owners are exempt from this policy." + "message": "Určite kedy budú ponúknuté položky pre automatické vypĺňanie. Správcovia a majitelia majú pre toto pravidlo výnimku." }, "uriMatchDetectionOptionsLabel": { - "message": "Default URI match detection" + "message": "Predvolené zisťovanie zhody URI" }, "invalidUriMatchDefaultPolicySetting": { - "message": "Please select a valid URI match detection option.", + "message": "Prosím vyberte platnú predvoľbu zisťovania zhody URI.", "description": "Error message displayed when a user attempts to save URI match detection policy settings with an invalid selection." }, "modifiedPolicyId": { @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Priradiť úlohy" }, - "assignTasksToMembers": { - "message": "Pre riadené riešenie problémov prideľte úlohy členom" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Prideliť k zbierkam" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "Nie sú vybrané žiadne kritické aplikácie" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Ste si istí, že chcete pokračovať?" } } diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 709baa2e36c..2f14c42dd95 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 876669827c8..6201f3a7e4e 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index dd41032a031..1d0ae7b0897 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Доделите задатке чланова да прате напредак" }, - "onceYouReviewApps": { - "message": "Када прегледате апликације и означите их као критичне, можете доделити задатке члановима за решавање ризичних ставки и пратити напредак овде" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Пошаљи подсетнике" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Није пронађена ниједна апликација за $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Увезите податке за пријаву своје организације да бисте почели да надгледате безбедносне ризике акредитива. Када увезете, добијате:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Одредите приоритете ризика" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Фокусирајте се на апликације које су најважније" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Водич за санацију" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Доделите вођене задатке члановима изложеним ризику да ротирају акредитиве у ризику" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Праћење напретка" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Пратите промене током времена да бисте показали безбедносна побољшања" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Покрените свој први извештај да бисте видели апликације" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Направите извештај о увиду у ризик да бисте анализирали апликације ваше организације и идентификовали ризичне лозинке на које треба обратити пажњу. Покретање вашег првог извештаја ће:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Нисте означили ниједну апликацију као критичну" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Чланови под ризиком" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Чланови са приступом за угрожене ставке критичних апликација" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Чланови са ризичним лозинкама" }, - "membersWillReceiveNotification": { - "message": "Чланови ће добити обавештење за решавање ризичних пријава путем екстензије претраживача." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "Угрожени чланови: $COUNT$", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ови чланови се пријављују у апликације са слабим, откривеним или поново коришћеним лозинкама." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Нема чланова пријављена у апликације са слабим, откривеним или поново коришћеним лозинкама." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Дајте приоритет критичним апликацијама" }, - "selectCriticalApplicationsDescription": { - "message": "Изаберите које су апликације најкритичније за вашу организацију, а затим доделите безбедносне задатке члановима да бисте решили ризике." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Прегледајте нове апликације" }, - "reviewNewApplicationsDescription": { - "message": "Истакли смо ризичне ставке за нове апликације ускладиштене у Админ конзоли које имају слабе, откривене или поново коришћене лозинке." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Кликните на икону звездице да бисте означили апликацију као критичну" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Додели задатке" }, - "assignTasksToMembers": { - "message": "Доделите задатке члановима за вођено решавање" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Додели колекцијама" diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index ca0547d7d95..3ebe0499428 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Tilldela medlemmar uppgifter för att övervaka framsteg" }, - "onceYouReviewApps": { - "message": "När du har granskat applikationer och markerat dem som kritiska kan du tilldela uppgifter till medlemmar för att åtgärda riskutsatta objekt och övervaka framsteg här" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Skicka påminnelser" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Inga applikationer hittades för $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "Ingen data hittades" }, - "noApplicationsInOrgDescription": { - "message": "Importera din organisations inloggningsdata för att börja övervaka säkerhetsrisker för autentiseringsuppgifter. När de har importerats kan du:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritera risker" + "feature1Title": { + "message": "Markera applikationer som kritiska" }, - "benefit1Description": { - "message": "Fokusera på applikationer som betyder mest" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide åtgärder" + "feature2Title": { + "message": "Hjälp medlemmar att förbättra sin säkerhet" }, - "benefit2Description": { - "message": "Tilldela riskutsatta medlemmar vägledda uppgifter för att byta ut riskutsatta autentiseringsuppgifter" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Övervaka framsteg" }, - "benefit3Description": { - "message": "Spåra förändringar över tid för att visa säkerhetsförbättringar" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Kör din första rapport för att se applikationer" + "noReportsRunTitle": { + "message": "Generera rapport" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Du har inte markerat några applikationer som kritiska" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Riskutsatta medlemmar" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ medlemmar i riskzonen", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Dessa medlemmar loggar in i applikationer med svaga, exponerade eller återanvända lösenord." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Det finns inga medlemmar som loggar in i applikationer med svaga, exponerade eller återanvända lösenord." @@ -386,19 +380,19 @@ "message": "Review applications to secure the items most critical to your organization's security" }, "reviewApplications": { - "message": "Review applications" + "message": "Granska applikationer" }, "prioritizeCriticalApplications": { "message": "Prioritera kritiska applikationer" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Granska nya applikationer" }, - "reviewNewApplicationsDescription": { - "message": "Vi har markerat objekt i riskzonen för nya applikationer som lagras i administratörskonsolen och som har svaga, exponerade eller återanvända lösenord." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -3251,7 +3245,7 @@ "message": "Nästa debitering" }, "nextChargeHeader": { - "message": "Next Charge" + "message": "Nästa betalning" }, "plan": { "message": "Plan" @@ -3260,7 +3254,7 @@ "message": "Detaljer" }, "discount": { - "message": "discount" + "message": "rabatt" }, "downloadLicense": { "message": "Hämta licens" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Tilldela uppgifter" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Skicka aviseringar om att ändra lösenord" }, "assignToCollections": { "message": "Tilldela till samlingar" @@ -12162,6 +12156,6 @@ "message": "No critical applications are selected" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Är du säker på att du vill fortsätta?" } } diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index bea4f3365bc..64ce03adb11 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "ஆபத்தில் உள்ள உறுப்பினர்கள்" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "இந்த உறுப்பினர்கள் பலவீனமான, அம்பலப்படுத்தப்பட்ட அல்லது மீண்டும் பயன்படுத்தப்பட்ட கடவுச்சொற்களுடன் பயன்பாடுகளில் உள்நுழைகிறார்கள்." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "பலவீனமான, அம்பலப்படுத்தப்பட்ட அல்லது மீண்டும் பயன்படுத்தப்பட்ட கடவுச்சொற்களுடன் பயன்பாடுகளில் உள்நுழையும் உறுப்பினர்கள் யாரும் இல்லை." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "சேகரிப்புகளுக்கு ஒதுக்கு" diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 47df4826851..9f57ed8c6ca 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 3e4797eb03f..30a75f4d4da 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "You haven’t marked any applications as critical" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "At-risk members" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "These members are logging into applications with weak, exposed, or reused passwords." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "These are no members logging into applications with weak, exposed, or reused passwords." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Assign to collections" diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index bb52d98e1e4..448172f3c43 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "$ORG NAME$ kuruluşu için hiçbir uygulama bulunmadı", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Hiçbir uygulamayı kritik olarak işaretlemediniz" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Riskli üyeler" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Kritik uygulamalar için risk altındaki kayıtlara erişimi olan üyeler" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ üye risk altında", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Bu üyeler uygulamalara zayıf, ele geçirilmiş veya tekrar kullanılan parolalarla giriş yapmaktadır." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Zayıf, ele geçirilmiş veya tekrar kullanılan parolayla uygulamalara giriş yapan üye yok." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Koleksiyonlara ata" @@ -12162,6 +12156,6 @@ "message": "No critical applications are selected" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "Devam etmek istediğinizden emin misiniz?" } } diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index 6fa971b98a5..f1f7d6530b7 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Assign members tasks to monitor progress" }, - "onceYouReviewApps": { - "message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Send reminders" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "No applications found for $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Prioritize risks" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Focus on applications that matter the most" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Guide remediation" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Assign at-risk members guided tasks to rotate at-risk credentials" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { + "feature3Title": { "message": "Monitor progress" }, - "benefit3Description": { - "message": "Track changes over time to show security improvements" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Run your first report to see applications" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Ви не відмітили жодного додатку в якості критичного" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Ризиковані учасники" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Members with access to at-risk items for critical applications" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Members with at-risk passwords" }, - "membersWillReceiveNotification": { - "message": "Members will receive a notification to resolve at-risk logins through the browser extension." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Ці учасники використовують у програмах слабкі, викриті, або повторювані паролі." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Немає учасників, які використовують у програмах слабкі, викриті, або повторювані паролі." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Prioritize critical applications" }, - "selectCriticalApplicationsDescription": { - "message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Click the star icon to mark an app as critical" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Assign tasks" }, - "assignTasksToMembers": { - "message": "Assign tasks to members for guided resolution" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Призначити до збірок" diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index 6e6ba52daad..f05004b46da 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "Giao tác vụ cho thành viên để theo dõi tiến trình" }, - "onceYouReviewApps": { - "message": "Sau khi bạn xem xét các ứng dụng và đánh dấu chúng là nghiêm trọng, bạn có thể giao nhiệm vụ cho các thành viên để xử lý các mục có rủi ro và theo dõi tiến độ tại đây" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "Gửi lời nhắc" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "Không tìm thấy ứng dụng nào cho $ORG NAME$", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "Nhập dữ liệu đăng nhập của tổ chức bạn để bắt đầu giám sát các rủi ro bảo mật thông tin xác thực. Sau khi nhập, bạn có thể:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "Ưu tiên các rủi ro" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "Tập trung vào các ứng dụng quan trọng nhất" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "Hướng dẫn khắc phục" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "Giao nhiệm vụ được hướng dẫn cho các thành viên có rủi ro để xoay vòng thông tin xác thực có rủi ro" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "Theo dõi tiến độ" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "Theo dõi thay đổi theo thời gian để hiển thị cải tiến bảo mật" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "Chạy báo cáo đầu tiên của bạn để xem các ứng dụng" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "Tạo báo cáo thông tin chi tiết về rủi ro để phân tích các ứng dụng của tổ chức bạn và xác định các mật khẩu có rủi ro cần chú ý. Khi chạy báo cáo đầu tiên, bạn sẽ:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "Bạn chưa đánh dấu ứng dụng nào là quan trọng" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "Các thành viên có rủi ro" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "Thành viên có quyền truy cập vào các mục có nguy cơ dành cho các ứng dụng quan trọng" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "Thành viên có mật khẩu rủi ro" }, - "membersWillReceiveNotification": { - "message": "Các thành viên sẽ nhận được thông báo để giải quyết các đăng nhập có rủi ro thông qua tiện ích mở rộng trình duyệt." + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ thành viên gặp rủi ro", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "Các thành viên này đang đăng nhập vào các ứng dụng bằng mật khẩu yếu, dễ bị lộ hoặc được sử dụng lại." + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "Không có thành viên nào đăng nhập vào ứng dụng bằng mật khẩu yếu, dễ bị lộ hoặc được sử dụng lại." @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "Ưu tiên các ứng dụng quan trọng" }, - "selectCriticalApplicationsDescription": { - "message": "Chọn những ứng dụng quan trọng nhất đối với tổ chức của bạn, sau đó giao nhiệm vụ bảo mật cho các thành viên để giải quyết rủi ro." + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" }, - "reviewNewApplicationsDescription": { - "message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords." + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "Nhấp vào biểu tượng ngôi sao để đánh dấu một ứng dụng là quan trọng" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "Giao tác vụ" }, - "assignTasksToMembers": { - "message": "Giao nhiệm vụ cho các thành viên để hướng dẫn giải quyết" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "Gán vào bộ sưu tập" diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index cce4544ac7b..05534b3a8b2 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "分配成员任务以监测进度" }, - "onceYouReviewApps": { - "message": "审查新应用程序并将其标记为关键后,您可以在这里将任务分配给成员以解决存在风险的项目并监测进度" + "onceYouReviewApplications": { + "message": "审查应用程序并将其标记为关键后,将任务分配给您的成员以更改他们的密码。" }, "sendReminders": { "message": "发送提醒" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "未找到与 $ORG NAME$ 相关的应用程序", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "未找到任何数据" }, - "noApplicationsInOrgDescription": { - "message": "导入您组织的登录数据,以开始监测凭据安全风险。导入后您将能够:" + "noDataInOrgDescription": { + "message": "导入您组织的登录数据以开始使用 Access Intelligence。导入完成后,您将能够:" }, - "benefit1Title": { - "message": "优先处理风险" + "feature1Title": { + "message": "将应用程序标记为关键" }, - "benefit1Description": { - "message": "重点关注最重要的应用程序" + "feature1Description": { + "message": "这将帮助您优先消除您最重要的应用程序的风险。" }, - "benefit2Title": { - "message": "指导补救措施" + "feature2Title": { + "message": "帮助成员提升他们的安全性" }, - "benefit2Description": { - "message": "为存在风险的成员分配引导式任务,以轮换存在风险的凭据" + "feature2Description": { + "message": "为存在风险的成员分配引导式安全任务,以更新凭证。" }, - "benefit3Title": { + "feature3Title": { "message": "监测进度" }, - "benefit3Description": { - "message": "追踪变化趋势,展示安全改进成效" + "feature3Description": { + "message": "追踪一段时间内的变化,以展示安全性的改进。" }, - "noReportRunTitle": { - "message": "运行您的第一份报告以查看应用程序" + "noReportsRunTitle": { + "message": "生成报告" }, - "noReportRunDescription": { - "message": "生成风险洞察报告,以分析您组织的应用程序并识别需要关注的风险密码。运行您的第一份报告将:" + "noReportsRunDescription": { + "message": "您已准备好开始生成报告。生成报告后,您将能够:" }, "noCriticalApplicationsTitle": { "message": "您还没有将任何应用程序标记为关键" @@ -224,7 +218,7 @@ "message": "选择关键应用程序" }, "markAppAsCritical": { - "message": "标记应用程序为关键" + "message": "将 App 标记为关键" }, "markAsCritical": { "message": "标记为关键" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "存在风险的成员" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "对关键应用程序中存在风险的项目具有访问权限的成员" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "这些成员拥有关键应用程序中易受攻击项目的访问权限。" }, "membersWithAtRiskPasswords": { "message": "密码存在风险的成员" }, - "membersWillReceiveNotification": { - "message": "成员将通过浏览器扩展收到通知,以解决存在风险的登录。" + "membersWillReceiveSecurityTask": { + "message": "您的组织成员将被分配更改易受攻击的密码的任务。他们将在 Bitwarden 浏览器扩展中收到通知。" }, "membersAtRiskCount": { "message": "$COUNT$ 个成员存在风险", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "这些成员正登录到具有弱的、暴露的或重复使用的密码的应用程序。" + "atRiskMemberDescription": { + "message": "这些成员正在登录到具有弱的、暴露的或重复使用的密码的关键应用程序。" }, "atRiskMembersDescriptionNone": { "message": "没有成员登录到具有弱的、暴露的或重复使用的密码的应用程序。" @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "优先处理关键应用程序" }, - "selectCriticalApplicationsDescription": { - "message": "选择对您的组织最关键的应用程序,然后将安全任务分配给成员以解决风险。" + "selectCriticalAppsDescription": { + "message": "选择对您的组织最关键的应用程序。然后,您可以将安全任务分配给成员以消除风险。" }, "reviewNewApplications": { "message": "审查新应用程序" }, - "reviewNewApplicationsDescription": { - "message": "我们突出显示了存储在管理控制台中的新应用程序的风险项目,这些项目使用了弱、暴露或重复使用的密码。" + "reviewNewAppsDescription": { + "message": "审查具有易受攻击项目的新应用程序然后将需要密切监测的应用程序标记为关键。然后,您可以将安全任务分配给成员以消除风险。" }, "clickIconToMarkAppAsCritical": { "message": "点击星形图标以将 App 标记为关键" @@ -2543,23 +2537,23 @@ "message": "恢复访问权限" }, "premium": { - "message": "高级会员", + "message": "高级版", "description": "Premium membership" }, "premiumMembership": { "message": "高级会员" }, "premiumRequired": { - "message": "需要高级会员" + "message": "需要高级版" }, "premiumRequiredDesc": { "message": "使用此功能需要高级会员资格。" }, "youHavePremiumAccess": { - "message": "您拥有高级访问权限" + "message": "您拥有高级版访问权限" }, "alreadyPremiumFromOrg": { - "message": "由于您是拥有高级会员功能的组织的成员,您已经拥有此功能。" + "message": "由于您是组织的成员,您已经拥有高级版功能访问权限。" }, "manage": { "message": "管理" @@ -3056,7 +3050,7 @@ "message": "您账户的信用额度可用于进行消费。任何可用的信用额度将用于自动抵扣此账户的账单。" }, "goPremium": { - "message": "升级高级会员", + "message": "升级高级版", "description": "Another way of saying \"Get a Premium membership\"" }, "premiumUpdated": { @@ -3084,7 +3078,7 @@ "message": "优先客户支持。" }, "premiumSignUpFuture": { - "message": "未来的更多高级功能。敬请期待!" + "message": "未来的更多高级版功能。敬请期待!" }, "premiumPrice": { "message": "所有功能仅需 $PRICE$ /年!", @@ -3096,7 +3090,7 @@ } }, "premiumPriceWithFamilyPlan": { - "message": "升级高级会员仅需 $PRICE$ /年,或成为具有 $FAMILYPLANUSERCOUNT$ 位用户以及无限制的家庭共享的高级账户,通过 ", + "message": "升级高级版仅需 $PRICE$ /年,或成为具有 $FAMILYPLANUSERCOUNT$ 位用户以及无限制的家庭共享的高级账户,通过 ", "placeholders": { "price": { "content": "$1", @@ -3115,10 +3109,10 @@ "message": "附加项目" }, "premiumAccess": { - "message": "高级会员" + "message": "高级版访问权限" }, "premiumAccessDesc": { - "message": "您可以为您的组织所有成员添加高级访问权限,只需 $PRICE$ /$INTERVAL$ 。", + "message": "您可以为您的组织所有成员添加高级版访问权限,只需 $PRICE$ /$INTERVAL$ 。", "placeholders": { "price": { "content": "$1", @@ -3559,7 +3553,7 @@ "message": "本地托管(可选)" }, "usersGetPremium": { - "message": "用户拥有高级会员功能权限" + "message": "用户拥有高级版功能访问权限" }, "controlAccessWithGroups": { "message": "使用群组控制用户访问权限" @@ -6915,7 +6909,7 @@ "message": "Bitwarden 家庭方案包含" }, "sponsoredFamiliesPremiumAccess": { - "message": "最多 6 个用户的高级访问权限" + "message": "最多 6 个高级版访问权限的用户" }, "sponsoredFamiliesSharedCollectionsForFamilyMembers": { "message": "为家庭成员提供共享集合" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "分配任务" }, - "assignTasksToMembers": { - "message": "将任务分配给成员以引导式解决" + "assignSecurityTasksToMembers": { + "message": "发送通知以更改密码" }, "assignToCollections": { "message": "分配到集合" @@ -10877,7 +10871,7 @@ "message": "保护您的家庭登录" }, "accessToPremiumFeatures": { - "message": "访问高级会员功能" + "message": "高级版功能访问权限" }, "additionalStorageGbMessage": { "message": "GB 附加存储" @@ -10916,7 +10910,7 @@ "message": "RSA 4096-Bit" }, "premiumAccounts": { - "message": "6 个高级账户" + "message": "6 个高级版账户" }, "unlimitedSharing": { "message": "不限数量的共享" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "未选择任何关键应用程序" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "确定要继续吗?" } } diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 784027a735a..3036366816a 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -93,8 +93,8 @@ "assignMembersTasksToMonitorProgress": { "message": "指派成員任務以監控進度" }, - "onceYouReviewApps": { - "message": "在您檢視應用程式並將其標記為關鍵後,您可以指派任務給成員,以解決有風險的項目,並在此監控進度" + "onceYouReviewApplications": { + "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." }, "sendReminders": { "message": "傳送提醒" @@ -178,41 +178,35 @@ } } }, - "noApplicationsInOrgTitle": { - "message": "未找到 $ORG NAME$ 的應用程式", - "placeholders": { - "org name": { - "content": "$1", - "example": "Company Name" - } - } + "noDataInOrgTitle": { + "message": "No data found" }, - "noApplicationsInOrgDescription": { - "message": "匯入您組織的登入資料以開始監控憑證安全風險。匯入後您可以:" + "noDataInOrgDescription": { + "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, - "benefit1Title": { - "message": "風險優先排序" + "feature1Title": { + "message": "Mark applications as critical" }, - "benefit1Description": { - "message": "專注於最重要的應用程式" + "feature1Description": { + "message": "This will help you remove risks to your most important applications first." }, - "benefit2Title": { - "message": "指導補救措施" + "feature2Title": { + "message": "Help members improve their security" }, - "benefit2Description": { - "message": "指派有風險的成員執行指導任務以輪換有風險的憑證" + "feature2Description": { + "message": "Assign at-risk members guided security tasks to update credentials." }, - "benefit3Title": { - "message": "監控進展" + "feature3Title": { + "message": "Monitor progress" }, - "benefit3Description": { - "message": "追蹤隨時間變化的狀況以顯示安全性改善" + "feature3Description": { + "message": "Track changes over time to show security improvements." }, - "noReportRunTitle": { - "message": "執行您的第一份報告以查看應用程式" + "noReportsRunTitle": { + "message": "Generate report" }, - "noReportRunDescription": { - "message": "產生風險洞察報告以分析組織的應用程式並找出需要注意的高風險密碼。執行第一份報告將會:" + "noReportsRunDescription": { + "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" }, "noCriticalApplicationsTitle": { "message": "您尚未將任何應用程式標記為關鍵" @@ -271,14 +265,14 @@ "atRiskMembers": { "message": "具有風險的成員" }, - "membersWithAccessToAtRiskItemsForCriticalApps": { - "message": "擁有關鍵應用程式中有風險項目存取權的成員" + "membersWithAccessToAtRiskItemsForCriticalApplications": { + "message": "These members have access to vulnerable items for critical applications." }, "membersWithAtRiskPasswords": { "message": "使用有風險密碼的成員" }, - "membersWillReceiveNotification": { - "message": "成員會透過瀏覽器擴充套件接收到有風險登入的通知。" + "membersWillReceiveSecurityTask": { + "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ 位有風險的成員", @@ -307,8 +301,8 @@ } } }, - "atRiskMembersDescription": { - "message": "這些成員正在使用弱密碼、外洩密碼或重複密碼登入應用程式。" + "atRiskMemberDescription": { + "message": "These members are logging into critical applications with weak, exposed, or reused passwords." }, "atRiskMembersDescriptionNone": { "message": "目前沒有成員使用弱密碼、外洩密碼或重複密碼登入應用程式。" @@ -391,14 +385,14 @@ "prioritizeCriticalApplications": { "message": "優先處理關鍵應用程式" }, - "selectCriticalApplicationsDescription": { - "message": "選擇對組織最關鍵的應用程式,然後將安全任務指派給成員以供解決。" + "selectCriticalAppsDescription": { + "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "審查新應用程式" }, - "reviewNewApplicationsDescription": { - "message": "我們已在管理主控台中標示出新應用程式中密碼薄弱、已外洩或重複使用的高風險項目。" + "reviewNewAppsDescription": { + "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." }, "clickIconToMarkAppAsCritical": { "message": "點擊星形圖示以將應用程式標記為關鍵" @@ -9856,8 +9850,8 @@ "assignTasks": { "message": "指派任務" }, - "assignTasksToMembers": { - "message": "指派任務給成員並引導解決" + "assignSecurityTasksToMembers": { + "message": "Send notifications to change passwords" }, "assignToCollections": { "message": "指派至集合" @@ -12159,9 +12153,9 @@ } }, "confirmNoSelectedCriticalApplicationsTitle": { - "message": "No critical applications are selected" + "message": "未選取任何關鍵應用程式" }, "confirmNoSelectedCriticalApplicationsDesc": { - "message": "Are you sure you want to continue?" + "message": "您確定要繼續嗎?" } } From 28dc244fd3bc272fd2e36c8727df76b27f75ede4 Mon Sep 17 00:00:00 2001 From: Brandon Treston <btreston@bitwarden.com> Date: Wed, 19 Nov 2025 13:32:50 -0500 Subject: [PATCH 180/249] fix error in console (#17468) --- .../group-name-badge.component.html | 2 +- .../group-badge/group-name-badge.component.ts | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html index 9ddc9897a31..a8021e82c39 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.html @@ -1 +1 @@ -<bit-badge-list [items]="groupNames" [maxItems]="3" variant="secondary"></bit-badge-list> +<bit-badge-list [items]="groupNames()" [maxItems]="3" variant="secondary"></bit-badge-list> diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 8a58f5b92d7..3c1d0d2b691 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -1,36 +1,33 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Input, OnChanges } from "@angular/core"; +import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { GroupView } from "../../core"; -// 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: "app-group-badge", templateUrl: "group-name-badge.component.html", standalone: false, + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class GroupNameBadgeComponent implements OnChanges { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() selectedGroups: SelectionReadOnlyRequest[]; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() allGroups: GroupView[]; +export class GroupNameBadgeComponent { + readonly selectedGroups = input<SelectionReadOnlyRequest[]>([]); + readonly allGroups = input<GroupView[]>([]); - protected groupNames: string[] = []; + protected readonly groupNames = computed(() => { + const allGroups = this.allGroups(); + if (!allGroups) { + return []; + } + + return this.selectedGroups() + .map((g) => { + return allGroups.find((o) => o.id === g.id)?.name; + }) + .filter((name): name is string => name !== undefined) + .sort(this.i18nService.collator.compare); + }); constructor(private i18nService: I18nService) {} - - ngOnChanges() { - this.groupNames = this.selectedGroups - .map((g) => { - return this.allGroups.find((o) => o.id === g.id)?.name; - }) - .sort(this.i18nService.collator.compare); - } } From 9ec05a96b9f14331f8c2dfde5513115cf238940f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:44:01 +0000 Subject: [PATCH 181/249] [deps]: Update GitHub Artifact Actions (#17305) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .github/workflows/build-browser.yml | 8 +-- .github/workflows/build-cli.yml | 12 ++--- .github/workflows/build-desktop.yml | 84 ++++++++++++++--------------- .github/workflows/build-web.yml | 2 +- .github/workflows/test.yml | 8 +-- 5 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 83e6c2d696e..dc71d33d605 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -193,7 +193,7 @@ jobs: zip -r browser-source.zip browser-source - name: Upload browser source - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{matrix.license_type.archive_name_prefix}}browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -268,7 +268,7 @@ jobs: npm --version - name: Download browser source - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: ${{matrix.license_type.source_archive_name_prefix}}browser-source-${{ env._BUILD_NUMBER }}.zip @@ -332,7 +332,7 @@ jobs: working-directory: browser-source/apps/browser - name: Upload extension artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ matrix.license_type.artifact_prefix }}${{ matrix.browser.artifact_name }}-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/${{matrix.license_type.archive_name_prefix}}${{ matrix.browser.archive_name }} @@ -506,7 +506,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{matrix.license_type.archive_name_prefix}}dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/${{matrix.license_type.archive_name_prefix}}dist-safari.zip diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 414c043b89e..babd00a323f 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -268,7 +268,7 @@ jobs: fi - name: Upload unix zip asset - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}${{ matrix.os.target_suffix }}-${{ env._PACKAGE_VERSION }}.zip @@ -482,7 +482,7 @@ jobs: } - name: Upload windows zip asset - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip @@ -490,7 +490,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -503,7 +503,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -535,7 +535,7 @@ jobs: echo "BW Package Version: $_PACKAGE_VERSION" - name: Get bw linux cli - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: bw-linux-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/snap @@ -572,7 +572,7 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 27a7bfe4124..1877374a525 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -257,35 +257,35 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -298,7 +298,7 @@ jobs: sudo npm run pack:lin:flatpak - name: Upload flatpak artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: com.bitwarden.desktop.flatpak path: apps/desktop/dist/com.bitwarden.desktop.flatpak @@ -426,14 +426,14 @@ jobs: run: npm run dist:lin:arm64 - name: Upload .snap artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_arm64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_arm64.snap if-no-files-found: error - name: Upload tar.gz artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_arm64.tar.gz path: apps/desktop/dist/bitwarden_desktop_arm64.tar.gz @@ -446,7 +446,7 @@ jobs: sudo npm run pack:lin:flatpak - name: Upload flatpak artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: com.bitwarden.desktop-arm64.flatpak path: apps/desktop/dist/com.bitwarden.desktop.flatpak @@ -617,7 +617,7 @@ jobs: -NewName bitwarden-$env:_PACKAGE_VERSION-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe @@ -625,7 +625,7 @@ jobs: - name: Upload installer exe artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}..exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe @@ -633,7 +633,7 @@ jobs: - name: Upload appx ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx @@ -641,7 +641,7 @@ jobs: - name: Upload store appx ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx @@ -649,7 +649,7 @@ jobs: - name: Upload NSIS ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z @@ -657,7 +657,7 @@ jobs: - name: Upload appx x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx @@ -665,7 +665,7 @@ jobs: - name: Upload store appx x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx @@ -673,7 +673,7 @@ jobs: - name: Upload NSIS x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z @@ -681,7 +681,7 @@ jobs: - name: Upload appx ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx @@ -689,7 +689,7 @@ jobs: - name: Upload store appx ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx @@ -697,7 +697,7 @@ jobs: - name: Upload NSIS ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z @@ -705,7 +705,7 @@ jobs: - name: Upload nupkg artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg @@ -713,7 +713,7 @@ jobs: - name: Upload auto-update artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -868,7 +868,7 @@ jobs: -NewName latest-beta.yml - name: Upload portable exe artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Beta-Portable-${{ env._PACKAGE_VERSION }}.exe @@ -876,7 +876,7 @@ jobs: - name: Upload installer exe artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Beta-Installer-${{ env._PACKAGE_VERSION }}.exe @@ -884,7 +884,7 @@ jobs: - name: Upload appx ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-ia32.appx @@ -892,7 +892,7 @@ jobs: - name: Upload store appx ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-ia32-store.appx @@ -900,7 +900,7 @@ jobs: - name: Upload NSIS ia32 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-beta-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-beta-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z @@ -908,7 +908,7 @@ jobs: - name: Upload appx x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-x64.appx @@ -916,7 +916,7 @@ jobs: - name: Upload store appx x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-x64-store.appx @@ -924,7 +924,7 @@ jobs: - name: Upload NSIS x64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-beta-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-beta-${{ env._PACKAGE_VERSION }}-x64.nsis.7z @@ -932,7 +932,7 @@ jobs: - name: Upload appx ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-arm64.appx @@ -940,7 +940,7 @@ jobs: - name: Upload store appx ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-Beta-${{ env._PACKAGE_VERSION }}-arm64-store.appx @@ -948,7 +948,7 @@ jobs: - name: Upload NSIS ARM64 artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: bitwarden-beta-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-beta-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z @@ -956,7 +956,7 @@ jobs: - name: Upload auto-update artifact if: ${{ needs.setup.outputs.has_secrets == 'true' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: latest-beta.yml path: apps/desktop/dist/nsis-web/latest-beta.yml @@ -1408,7 +1408,7 @@ jobs: run: npm run build - name: Download Browser artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: path: ${{ github.workspace }}/browser-build-artifacts @@ -1441,28 +1441,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -1688,7 +1688,7 @@ jobs: run: npm run build - name: Download Browser artifact - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: path: ${{ github.workspace }}/browser-build-artifacts @@ -1731,14 +1731,14 @@ jobs: $buildInfo | ConvertTo-Json | Set-Content -Path dist/macos-build-number.json - name: Upload MacOS App Store build number artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: macos-build-number.json path: apps/desktop/dist/macos-build-number.json if-no-files-found: error - name: Upload .pkg artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 89dd684c5f0..caf806af9f0 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -307,7 +307,7 @@ jobs: zip -r web-$_VERSION-${{ matrix.artifact_name }}.zip build - name: Upload ${{ matrix.artifact_name }} artifact - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.artifact_name }}.zip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed967e63b5a..f471826355f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -74,7 +74,7 @@ jobs: uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1 - name: Upload test coverage - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: jest-coverage path: ./coverage/lcov.info @@ -160,7 +160,7 @@ jobs: run: cargo llvm-cov --all-features --lcov --output-path lcov.info --workspace --no-cfg-coverage - name: Upload test coverage - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: rust-coverage path: ./apps/desktop/desktop_native/lcov.info @@ -178,13 +178,13 @@ jobs: persist-credentials: false - name: Download jest coverage - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: jest-coverage path: ./ - name: Download rust coverage - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: rust-coverage path: ./apps/desktop/desktop_native From de42cf303fd3a567463e474996a9917b57594805 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 19 Nov 2025 12:57:00 -0600 Subject: [PATCH 182/249] [PM-27925] Refactor `StripeService` to allow more than one instance (#17467) * Refactor StripeService to allow more than one instance per scope * Fix linting issue * Claude's feedback --- .../settings/create-organization.component.ts | 15 +- .../enter-payment-method.component.ts | 154 ++-- .../billing/services/stripe.service.spec.ts | 797 ++++++++++++++++++ .../app/billing/services/stripe.service.ts | 259 ++++-- 4 files changed, 1088 insertions(+), 137 deletions(-) create mode 100644 apps/web/src/app/billing/services/stripe.service.spec.ts diff --git a/apps/web/src/app/admin-console/settings/create-organization.component.ts b/apps/web/src/app/admin-console/settings/create-organization.component.ts index 45ce89c0e3d..78398fd8897 100644 --- a/apps/web/src/app/admin-console/settings/create-organization.component.ts +++ b/apps/web/src/app/admin-console/settings/create-organization.component.ts @@ -1,8 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { Subject, takeUntil } from "rxjs"; import { first } from "rxjs/operators"; import { PlanType, ProductTierType, ProductType } from "@bitwarden/common/billing/enums"; @@ -19,7 +19,7 @@ import { SharedModule } from "../../shared"; templateUrl: "create-organization.component.html", imports: [SharedModule, OrganizationPlansComponent, HeaderModule], }) -export class CreateOrganizationComponent implements OnInit { +export class CreateOrganizationComponent implements OnInit, OnDestroy { protected secretsManager = false; protected plan: PlanType = PlanType.Free; protected productTier: ProductTierType = ProductTierType.Free; @@ -29,6 +29,8 @@ export class CreateOrganizationComponent implements OnInit { private configService: ConfigService, ) {} + private destroy$ = new Subject<void>(); + async ngOnInit(): Promise<void> { const milestone3FeatureEnabled = await this.configService.getFeatureFlag( FeatureFlag.PM26462_Milestone_3, @@ -37,7 +39,7 @@ export class CreateOrganizationComponent implements OnInit { ? PlanType.FamiliesAnnually : PlanType.FamiliesAnnually2025; - this.route.queryParams.pipe(first(), takeUntilDestroyed()).subscribe((qParams) => { + this.route.queryParams.pipe(first(), takeUntil(this.destroy$)).subscribe((qParams) => { if (qParams.plan === "families" || qParams.productTier == ProductTierType.Families) { this.plan = familyPlan; this.productTier = ProductTierType.Families; @@ -61,4 +63,9 @@ export class CreateOrganizationComponent implements OnInit { this.secretsManager = qParams.product == ProductType.SecretsManager; }); } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } } diff --git a/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts b/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts index 5448f03aa56..9e7b870579d 100644 --- a/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts +++ b/apps/web/src/app/billing/payment/components/enter-payment-method.component.ts @@ -1,9 +1,10 @@ -import { Component, Input, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, input, OnDestroy, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { map, Observable, of, startWith, Subject, takeUntil } from "rxjs"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PopoverModule, ToastService } from "@bitwarden/components"; import { SharedModule } from "../../../shared"; @@ -34,18 +35,17 @@ type PaymentMethodFormGroup = FormGroup<{ }>; }>; -// 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: "app-enter-payment-method", + changeDetection: ChangeDetectionStrategy.OnPush, template: ` - @let showBillingDetails = includeBillingAddress && selected !== "payPal"; - <form [formGroup]="group"> + @let showBillingDetails = includeBillingAddress() && selected !== "payPal"; + <form [formGroup]="group()"> @if (showBillingDetails) { <h5 bitTypography="h5">{{ "paymentMethod" | i18n }}</h5> } <div class="tw-mb-4 tw-text-lg"> - <bit-radio-group [formControl]="group.controls.type"> + <bit-radio-group [formControl]="group().controls.type"> <bit-radio-button id="card-payment-method" [value]="'card'"> <bit-label> <i class="bwi bwi-fw bwi-credit-card" aria-hidden="true"></i> @@ -60,7 +60,7 @@ type PaymentMethodFormGroup = FormGroup<{ </bit-label> </bit-radio-button> } - @if (showPayPal) { + @if (showPayPal()) { <bit-radio-button id="paypal-payment-method" [value]="'payPal'"> <bit-label> <i class="bwi bwi-fw bwi-paypal" aria-hidden="true"></i> @@ -68,7 +68,7 @@ type PaymentMethodFormGroup = FormGroup<{ </bit-label> </bit-radio-button> } - @if (showAccountCredit) { + @if (showAccountCredit()) { <bit-radio-button id="credit-payment-method" [value]="'accountCredit'"> <bit-label> <i class="bwi bwi-fw bwi-dollar" aria-hidden="true"></i> @@ -82,10 +82,10 @@ type PaymentMethodFormGroup = FormGroup<{ @case ("card") { <div class="tw-grid tw-grid-cols-2 tw-gap-4 tw-mb-4"> <div class="tw-col-span-1"> - <app-payment-label for="stripe-card-number" required> + <app-payment-label [for]="'stripe-card-number-' + instanceId" required> {{ "cardNumberLabel" | i18n }} </app-payment-label> - <div id="stripe-card-number" class="tw-stripe-form-control"></div> + <div [id]="'stripe-card-number-' + instanceId" class="tw-stripe-form-control"></div> </div> <div class="tw-col-span-1 tw-flex tw-items-end"> <img @@ -95,13 +95,13 @@ type PaymentMethodFormGroup = FormGroup<{ /> </div> <div class="tw-col-span-1"> - <app-payment-label for="stripe-card-expiry" required> + <app-payment-label [for]="'stripe-card-expiry-' + instanceId" required> {{ "expiration" | i18n }} </app-payment-label> - <div id="stripe-card-expiry" class="tw-stripe-form-control"></div> + <div [id]="'stripe-card-expiry-' + instanceId" class="tw-stripe-form-control"></div> </div> <div class="tw-col-span-1"> - <app-payment-label for="stripe-card-cvc" required> + <app-payment-label [for]="'stripe-card-cvc-' + instanceId" required> {{ "securityCodeSlashCVV" | i18n }} <button [bitPopoverTriggerFor]="cardSecurityCodePopover" @@ -115,7 +115,7 @@ type PaymentMethodFormGroup = FormGroup<{ <p class="tw-mb-0">{{ "cardSecurityCodeDescription" | i18n }}</p> </bit-popover> </app-payment-label> - <div id="stripe-card-cvc" class="tw-stripe-form-control"></div> + <div [id]="'stripe-card-cvc-' + instanceId" class="tw-stripe-form-control"></div> </div> </div> } @@ -131,7 +131,7 @@ type PaymentMethodFormGroup = FormGroup<{ bitInput id="routingNumber" type="text" - [formControl]="group.controls.bankAccount.controls.routingNumber" + [formControl]="group().controls.bankAccount.controls.routingNumber" required /> </bit-form-field> @@ -141,7 +141,7 @@ type PaymentMethodFormGroup = FormGroup<{ bitInput id="accountNumber" type="text" - [formControl]="group.controls.bankAccount.controls.accountNumber" + [formControl]="group().controls.bankAccount.controls.accountNumber" required /> </bit-form-field> @@ -151,7 +151,7 @@ type PaymentMethodFormGroup = FormGroup<{ id="accountHolderName" bitInput type="text" - [formControl]="group.controls.bankAccount.controls.accountHolderName" + [formControl]="group().controls.bankAccount.controls.accountHolderName" required /> </bit-form-field> @@ -159,7 +159,7 @@ type PaymentMethodFormGroup = FormGroup<{ <bit-label>{{ "bankAccountType" | i18n }}</bit-label> <bit-select id="accountHolderType" - [formControl]="group.controls.bankAccount.controls.accountHolderType" + [formControl]="group().controls.bankAccount.controls.accountHolderType" required > <bit-option [value]="''" label="-- {{ 'select' | i18n }} --"></bit-option> @@ -186,7 +186,7 @@ type PaymentMethodFormGroup = FormGroup<{ } @case ("accountCredit") { <ng-container> - @if (hasEnoughAccountCredit) { + @if (hasEnoughAccountCredit()) { <bit-callout type="info"> {{ "makeSureEnoughCredit" | i18n }} </bit-callout> @@ -204,7 +204,7 @@ type PaymentMethodFormGroup = FormGroup<{ <div class="tw-col-span-6"> <bit-form-field [disableMargin]="true"> <bit-label>{{ "country" | i18n }}</bit-label> - <bit-select [formControl]="group.controls.billingAddress.controls.country"> + <bit-select [formControl]="group().controls.billingAddress.controls.country"> @for (selectableCountry of selectableCountries; track selectableCountry.value) { <bit-option [value]="selectableCountry.value" @@ -221,7 +221,7 @@ type PaymentMethodFormGroup = FormGroup<{ <input bitInput type="text" - [formControl]="group.controls.billingAddress.controls.postalCode" + [formControl]="group().controls.billingAddress.controls.postalCode" autocomplete="postal-code" /> </bit-form-field> @@ -233,26 +233,15 @@ type PaymentMethodFormGroup = FormGroup<{ standalone: true, imports: [BillingServicesModule, PaymentLabelComponent, PopoverModule, SharedModule], }) -export class EnterPaymentMethodComponent implements OnInit { - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input({ required: true }) group!: PaymentMethodFormGroup; +export class EnterPaymentMethodComponent implements OnInit, OnDestroy { + protected readonly instanceId = Utils.newGuid(); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() private showBankAccount = true; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() showPayPal = true; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() showAccountCredit = false; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() hasEnoughAccountCredit = true; - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-signals - @Input() includeBillingAddress = false; + readonly group = input.required<PaymentMethodFormGroup>(); + protected readonly showBankAccount = input(true); + readonly showPayPal = input(true); + readonly showAccountCredit = input(false); + readonly hasEnoughAccountCredit = input(true); + readonly includeBillingAddress = input(false); protected showBankAccount$!: Observable<boolean>; protected selectableCountries = selectableCountries; @@ -269,57 +258,62 @@ export class EnterPaymentMethodComponent implements OnInit { ngOnInit() { this.stripeService.loadStripe( + this.instanceId, { - cardNumber: "#stripe-card-number", - cardExpiry: "#stripe-card-expiry", - cardCvc: "#stripe-card-cvc", + cardNumber: `#stripe-card-number-${this.instanceId}`, + cardExpiry: `#stripe-card-expiry-${this.instanceId}`, + cardCvc: `#stripe-card-cvc-${this.instanceId}`, }, true, ); - if (this.showPayPal) { + if (this.showPayPal()) { this.braintreeService.loadBraintree("#braintree-container", false); } - if (!this.includeBillingAddress) { - this.showBankAccount$ = of(this.showBankAccount); - this.group.controls.billingAddress.disable(); + if (!this.includeBillingAddress()) { + this.showBankAccount$ = of(this.showBankAccount()); + this.group().controls.billingAddress.disable(); } else { - this.group.controls.billingAddress.patchValue({ + this.group().controls.billingAddress.patchValue({ country: "US", }); - this.showBankAccount$ = this.group.controls.billingAddress.controls.country.valueChanges.pipe( - startWith(this.group.controls.billingAddress.controls.country.value), - map((country) => this.showBankAccount && country === "US"), - ); + this.showBankAccount$ = + this.group().controls.billingAddress.controls.country.valueChanges.pipe( + startWith(this.group().controls.billingAddress.controls.country.value), + map((country) => this.showBankAccount() && country === "US"), + ); } - this.group.controls.type.valueChanges - .pipe(startWith(this.group.controls.type.value), takeUntil(this.destroy$)) + this.group() + .controls.type.valueChanges.pipe( + startWith(this.group().controls.type.value), + takeUntil(this.destroy$), + ) .subscribe((selected) => { if (selected === "bankAccount") { - this.group.controls.bankAccount.enable(); - if (this.includeBillingAddress) { - this.group.controls.billingAddress.enable(); + this.group().controls.bankAccount.enable(); + if (this.includeBillingAddress()) { + this.group().controls.billingAddress.enable(); } } else { switch (selected) { case "card": { - this.stripeService.mountElements(); - if (this.includeBillingAddress) { - this.group.controls.billingAddress.enable(); + this.stripeService.mountElements(this.instanceId); + if (this.includeBillingAddress()) { + this.group().controls.billingAddress.enable(); } break; } case "payPal": { this.braintreeService.createDropin(); - if (this.includeBillingAddress) { - this.group.controls.billingAddress.disable(); + if (this.includeBillingAddress()) { + this.group().controls.billingAddress.disable(); } break; } } - this.group.controls.bankAccount.disable(); + this.group().controls.bankAccount.disable(); } }); @@ -330,22 +324,28 @@ export class EnterPaymentMethodComponent implements OnInit { }); } + ngOnDestroy() { + this.stripeService.unloadStripe(this.instanceId); + this.destroy$.next(); + this.destroy$.complete(); + } + select = (paymentMethod: PaymentMethodOption) => - this.group.controls.type.patchValue(paymentMethod); + this.group().controls.type.patchValue(paymentMethod); tokenize = async (): Promise<TokenizedPaymentMethod | null> => { const exchange = async (paymentMethod: TokenizablePaymentMethod) => { switch (paymentMethod) { case "bankAccount": { - this.group.controls.bankAccount.markAllAsTouched(); - if (!this.group.controls.bankAccount.valid) { + this.group().controls.bankAccount.markAllAsTouched(); + if (!this.group().controls.bankAccount.valid) { throw new Error("Attempted to tokenize invalid bank account information."); } - const bankAccount = this.group.controls.bankAccount.getRawValue(); + const bankAccount = this.group().controls.bankAccount.getRawValue(); const clientSecret = await this.stripeService.createSetupIntent("bankAccount"); - const billingDetails = this.group.controls.billingAddress.enabled - ? this.group.controls.billingAddress.getRawValue() + const billingDetails = this.group().controls.billingAddress.enabled + ? this.group().controls.billingAddress.getRawValue() : undefined; return await this.stripeService.setupBankAccountPaymentMethod( clientSecret, @@ -355,10 +355,14 @@ export class EnterPaymentMethodComponent implements OnInit { } case "card": { const clientSecret = await this.stripeService.createSetupIntent("card"); - const billingDetails = this.group.controls.billingAddress.enabled - ? this.group.controls.billingAddress.getRawValue() + const billingDetails = this.group().controls.billingAddress.enabled + ? this.group().controls.billingAddress.getRawValue() : undefined; - return this.stripeService.setupCardPaymentMethod(clientSecret, billingDetails); + return this.stripeService.setupCardPaymentMethod( + this.instanceId, + clientSecret, + billingDetails, + ); } case "payPal": { return this.braintreeService.requestPaymentMethod(); @@ -410,15 +414,15 @@ export class EnterPaymentMethodComponent implements OnInit { validate = (): boolean => { if (this.selected === "bankAccount") { - this.group.controls.bankAccount.markAllAsTouched(); - return this.group.controls.bankAccount.valid; + this.group().controls.bankAccount.markAllAsTouched(); + return this.group().controls.bankAccount.valid; } return true; }; get selected(): PaymentMethodOption { - return this.group.value.type!; + return this.group().value.type!; } static getFormGroup = (): PaymentMethodFormGroup => diff --git a/apps/web/src/app/billing/services/stripe.service.spec.ts b/apps/web/src/app/billing/services/stripe.service.spec.ts new file mode 100644 index 00000000000..983aeb266ae --- /dev/null +++ b/apps/web/src/app/billing/services/stripe.service.spec.ts @@ -0,0 +1,797 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { BankAccount } from "@bitwarden/common/billing/models/domain"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; + +import { StripeService } from "./stripe.service"; + +// Extend Window interface to include Stripe +declare global { + interface Window { + Stripe: any; + } +} + +describe("StripeService", () => { + let service: StripeService; + let apiService: MockProxy<ApiService>; + let logService: MockProxy<LogService>; + + // Stripe SDK mocks + let mockStripeInstance: any; + let mockElements: any; + let mockCardNumber: any; + let mockCardExpiry: any; + let mockCardCvc: any; + + // DOM mocks + let mockScript: HTMLScriptElement; + let mockIframe: HTMLIFrameElement; + + beforeEach(() => { + jest.useFakeTimers(); + + // Setup service dependency mocks + apiService = mock<ApiService>(); + logService = mock<LogService>(); + + // Setup Stripe element mocks + mockCardNumber = { + mount: jest.fn(), + unmount: jest.fn(), + }; + mockCardExpiry = { + mount: jest.fn(), + unmount: jest.fn(), + }; + mockCardCvc = { + mount: jest.fn(), + unmount: jest.fn(), + }; + + // Setup Stripe Elements mock + mockElements = { + create: jest.fn((type: string) => { + switch (type) { + case "cardNumber": + return mockCardNumber; + case "cardExpiry": + return mockCardExpiry; + case "cardCvc": + return mockCardCvc; + default: + return null; + } + }), + getElement: jest.fn((type: string) => { + switch (type) { + case "cardNumber": + return mockCardNumber; + case "cardExpiry": + return mockCardExpiry; + case "cardCvc": + return mockCardCvc; + default: + return null; + } + }), + }; + + // Setup Stripe instance mock + mockStripeInstance = { + elements: jest.fn(() => mockElements), + confirmCardSetup: jest.fn(), + confirmUsBankAccountSetup: jest.fn(), + }; + + // Setup window.Stripe mock + window.Stripe = jest.fn(() => mockStripeInstance); + + // Setup DOM mocks + mockScript = { + id: "", + src: "", + onload: null, + onerror: null, + } as any; + + mockIframe = { + src: "https://js.stripe.com/v3/", + remove: jest.fn(), + } as any; + + jest.spyOn(window.document, "createElement").mockReturnValue(mockScript); + jest.spyOn(window.document, "getElementById").mockReturnValue(null); + jest.spyOn(window.document.head, "appendChild").mockReturnValue(mockScript); + jest.spyOn(window.document.head, "removeChild").mockImplementation(() => mockScript); + jest.spyOn(window.document, "querySelectorAll").mockReturnValue([mockIframe] as any); + + // Mock getComputedStyle + jest.spyOn(window, "getComputedStyle").mockReturnValue({ + getPropertyValue: (prop: string) => { + const props: Record<string, string> = { + "--color-text-main": "0, 0, 0", + "--color-text-muted": "128, 128, 128", + "--color-danger-600": "220, 38, 38", + }; + return props[prop] || ""; + }, + } as any); + + // Create service instance + service = new StripeService(apiService, logService); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + jest.restoreAllMocks(); + }); + + // Helper function to trigger script load + const triggerScriptLoad = () => { + if (mockScript.onload) { + mockScript.onload(new Event("load")); + } + }; + + // Helper function to advance timers and flush promises + const advanceTimersAndFlush = async (ms: number) => { + jest.advanceTimersByTime(ms); + await Promise.resolve(); + }; + + describe("createSetupIntent", () => { + it("should call API with correct path for card payment", async () => { + apiService.send.mockResolvedValue("client_secret_card_123"); + + const result = await service.createSetupIntent("card"); + + expect(apiService.send).toHaveBeenCalledWith("POST", "/setup-intent/card", null, true, true); + expect(result).toBe("client_secret_card_123"); + }); + + it("should call API with correct path for bank account payment", async () => { + apiService.send.mockResolvedValue("client_secret_bank_456"); + + const result = await service.createSetupIntent("bankAccount"); + + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/setup-intent/bank-account", + null, + true, + true, + ); + expect(result).toBe("client_secret_bank_456"); + }); + + it("should return client secret from API response", async () => { + const expectedSecret = "seti_1234567890_secret_abcdefg"; + apiService.send.mockResolvedValue(expectedSecret); + + const result = await service.createSetupIntent("card"); + + expect(result).toBe(expectedSecret); + }); + + it("should propagate API errors", async () => { + const error = new Error("API error"); + apiService.send.mockRejectedValue(error); + + await expect(service.createSetupIntent("card")).rejects.toThrow("API error"); + }); + }); + + describe("loadStripe - initial load", () => { + const instanceId = "test-instance-1"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + it("should create script element with correct attributes", () => { + service.loadStripe(instanceId, elementIds, false); + + expect(window.document.createElement).toHaveBeenCalledWith("script"); + expect(mockScript.id).toBe("stripe-script"); + expect(mockScript.src).toBe("https://js.stripe.com/v3?advancedFraudSignals=false"); + }); + + it("should append script to document head", () => { + service.loadStripe(instanceId, elementIds, false); + + expect(window.document.head.appendChild).toHaveBeenCalledWith(mockScript); + }); + + it("should initialize Stripe client on script load", async () => { + service.loadStripe(instanceId, elementIds, false); + + triggerScriptLoad(); + await advanceTimersAndFlush(0); + + expect(window.Stripe).toHaveBeenCalledWith(process.env.STRIPE_KEY); + }); + + it("should create Elements instance and store in Map", async () => { + service.loadStripe(instanceId, elementIds, false); + + triggerScriptLoad(); + await advanceTimersAndFlush(50); + + expect(mockStripeInstance.elements).toHaveBeenCalled(); + expect(service["instances"].size).toBe(1); + expect(service["instances"].get(instanceId)).toBeDefined(); + }); + + it("should increment instanceCount", async () => { + service.loadStripe(instanceId, elementIds, false); + + triggerScriptLoad(); + await advanceTimersAndFlush(50); + + expect(service["instanceCount"]).toBe(1); + }); + }); + + describe("loadStripe - already loaded", () => { + const instanceId1 = "instance-1"; + const instanceId2 = "instance-2"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + beforeEach(async () => { + // Load first instance to initialize Stripe + service.loadStripe(instanceId1, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + }); + + it("should not create new script if already loaded", () => { + jest.clearAllMocks(); + + service.loadStripe(instanceId2, elementIds, false); + + expect(window.document.createElement).not.toHaveBeenCalled(); + expect(window.document.head.appendChild).not.toHaveBeenCalled(); + }); + + it("should immediately initialize instance when script loaded", async () => { + service.loadStripe(instanceId2, elementIds, false); + await advanceTimersAndFlush(50); + + expect(service["instances"].size).toBe(2); + expect(service["instances"].get(instanceId2)).toBeDefined(); + }); + + it("should increment instanceCount correctly", async () => { + expect(service["instanceCount"]).toBe(1); + + service.loadStripe(instanceId2, elementIds, false); + await advanceTimersAndFlush(50); + + expect(service["instanceCount"]).toBe(2); + }); + }); + + describe("loadStripe - concurrent calls", () => { + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + it("should handle multiple loadStripe calls sequentially", async () => { + // Test practical scenario: load instances one after another + service.loadStripe("instance-1", elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + service.loadStripe("instance-2", elementIds, false); + await advanceTimersAndFlush(100); + + service.loadStripe("instance-3", elementIds, false); + await advanceTimersAndFlush(100); + + // All instances should be initialized + expect(service["instances"].size).toBe(3); + expect(service["instanceCount"]).toBe(3); + expect(service["instances"].get("instance-1")).toBeDefined(); + expect(service["instances"].get("instance-2")).toBeDefined(); + expect(service["instances"].get("instance-3")).toBeDefined(); + }); + + it("should share Stripe client across instances", async () => { + // Load first instance + service.loadStripe("instance-1", elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + const stripeClientAfterFirst = service["stripe"]; + expect(stripeClientAfterFirst).toBeDefined(); + + // Load second instance + service.loadStripe("instance-2", elementIds, false); + await advanceTimersAndFlush(100); + + // Should reuse the same Stripe client + expect(service["stripe"]).toBe(stripeClientAfterFirst); + expect(service["instances"].size).toBe(2); + }); + }); + + describe("mountElements - success path", () => { + const instanceId = "mount-test-instance"; + const elementIds = { + cardNumber: "#card-number-mount", + cardExpiry: "#card-expiry-mount", + cardCvc: "#card-cvc-mount", + }; + + beforeEach(async () => { + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + }); + + it("should mount all three card elements to DOM", async () => { + service.mountElements(instanceId); + await advanceTimersAndFlush(100); + + expect(mockCardNumber.mount).toHaveBeenCalledWith("#card-number-mount"); + expect(mockCardExpiry.mount).toHaveBeenCalledWith("#card-expiry-mount"); + expect(mockCardCvc.mount).toHaveBeenCalledWith("#card-cvc-mount"); + }); + + it("should use correct element IDs from instance", async () => { + const customIds = { + cardNumber: "#custom-card", + cardExpiry: "#custom-expiry", + cardCvc: "#custom-cvc", + }; + + service.loadStripe("custom-instance", customIds, false); + await advanceTimersAndFlush(100); + + service.mountElements("custom-instance"); + await advanceTimersAndFlush(100); + + expect(mockCardNumber.mount).toHaveBeenCalledWith("#custom-card"); + expect(mockCardExpiry.mount).toHaveBeenCalledWith("#custom-expiry"); + expect(mockCardCvc.mount).toHaveBeenCalledWith("#custom-cvc"); + }); + + it("should handle autoMount flag correctly", async () => { + const autoMountId = "auto-mount-instance"; + jest.clearAllMocks(); + + service.loadStripe(autoMountId, elementIds, true); + triggerScriptLoad(); + await advanceTimersAndFlush(150); + + // Should auto-mount without explicit call + expect(mockCardNumber.mount).toHaveBeenCalled(); + expect(mockCardExpiry.mount).toHaveBeenCalled(); + expect(mockCardCvc.mount).toHaveBeenCalled(); + }); + }); + + describe("mountElements - retry logic", () => { + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + it("should retry if instance not found", async () => { + service.mountElements("non-existent-instance"); + await advanceTimersAndFlush(100); + + expect(logService.warning).toHaveBeenCalledWith( + expect.stringContaining("Stripe instance non-existent-instance not found"), + ); + }); + + it("should log error after 10 failed attempts", async () => { + service.mountElements("non-existent-instance"); + + for (let i = 0; i < 10; i++) { + await advanceTimersAndFlush(100); + } + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("not found after 10 attempts"), + ); + }); + + it("should retry if elements not ready", async () => { + const instanceId = "retry-elements-instance"; + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + // Make elements temporarily unavailable + mockElements.getElement.mockReturnValueOnce(null); + mockElements.getElement.mockReturnValueOnce(null); + mockElements.getElement.mockReturnValueOnce(null); + + service.mountElements(instanceId); + await advanceTimersAndFlush(100); + + expect(logService.warning).toHaveBeenCalledWith( + expect.stringContaining("Some Stripe card elements"), + ); + }); + }); + + describe("setupCardPaymentMethod", () => { + const instanceId = "card-setup-instance"; + const clientSecret = "seti_card_secret_123"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + beforeEach(async () => { + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + }); + + it("should call Stripe confirmCardSetup with correct parameters", async () => { + mockStripeInstance.confirmCardSetup.mockResolvedValue({ + setupIntent: { status: "succeeded", payment_method: "pm_card_123" }, + }); + + await service.setupCardPaymentMethod(instanceId, clientSecret); + + expect(mockStripeInstance.confirmCardSetup).toHaveBeenCalledWith(clientSecret, { + payment_method: { + card: mockCardNumber, + }, + }); + }); + + it("should include billing details when provided", async () => { + mockStripeInstance.confirmCardSetup.mockResolvedValue({ + setupIntent: { status: "succeeded", payment_method: "pm_card_123" }, + }); + + const billingDetails = { country: "US", postalCode: "12345" }; + await service.setupCardPaymentMethod(instanceId, clientSecret, billingDetails); + + expect(mockStripeInstance.confirmCardSetup).toHaveBeenCalledWith(clientSecret, { + payment_method: { + card: mockCardNumber, + billing_details: { + address: { + country: "US", + postal_code: "12345", + }, + }, + }, + }); + }); + + it("should throw error if instance not found", async () => { + await expect(service.setupCardPaymentMethod("non-existent", clientSecret)).rejects.toThrow( + "Payment method initialization failed. Please try again.", + ); + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Stripe instance non-existent not found"), + ); + }); + + it("should throw error if setup fails", async () => { + const error = { message: "Card declined" }; + mockStripeInstance.confirmCardSetup.mockResolvedValue({ error }); + + await expect(service.setupCardPaymentMethod(instanceId, clientSecret)).rejects.toEqual(error); + expect(logService.error).toHaveBeenCalledWith(error); + }); + + it("should throw error if status is not succeeded", async () => { + const error = { message: "Invalid status" }; + mockStripeInstance.confirmCardSetup.mockResolvedValue({ + setupIntent: { status: "requires_action" }, + error, + }); + + await expect(service.setupCardPaymentMethod(instanceId, clientSecret)).rejects.toEqual(error); + }); + + it("should return payment method ID on success", async () => { + mockStripeInstance.confirmCardSetup.mockResolvedValue({ + setupIntent: { status: "succeeded", payment_method: "pm_card_success_123" }, + }); + + const result = await service.setupCardPaymentMethod(instanceId, clientSecret); + + expect(result).toBe("pm_card_success_123"); + }); + }); + + describe("setupBankAccountPaymentMethod", () => { + const clientSecret = "seti_bank_secret_456"; + const bankAccount: BankAccount = { + accountHolderName: "John Doe", + routingNumber: "110000000", + accountNumber: "000123456789", + accountHolderType: "individual", + }; + + beforeEach(async () => { + // Initialize Stripe instance for bank account tests + service.loadStripe( + "bank-test-instance", + { + cardNumber: "#card", + cardExpiry: "#expiry", + cardCvc: "#cvc", + }, + false, + ); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + }); + + it("should call Stripe confirmUsBankAccountSetup with bank details", async () => { + mockStripeInstance.confirmUsBankAccountSetup.mockResolvedValue({ + setupIntent: { status: "requires_action", payment_method: "pm_bank_123" }, + }); + + await service.setupBankAccountPaymentMethod(clientSecret, bankAccount); + + expect(mockStripeInstance.confirmUsBankAccountSetup).toHaveBeenCalledWith(clientSecret, { + payment_method: { + us_bank_account: { + routing_number: "110000000", + account_number: "000123456789", + account_holder_type: "individual", + }, + billing_details: { + name: "John Doe", + }, + }, + }); + }); + + it("should include billing address when provided", async () => { + mockStripeInstance.confirmUsBankAccountSetup.mockResolvedValue({ + setupIntent: { status: "requires_action", payment_method: "pm_bank_123" }, + }); + + const billingDetails = { country: "US", postalCode: "90210" }; + await service.setupBankAccountPaymentMethod(clientSecret, bankAccount, billingDetails); + + expect(mockStripeInstance.confirmUsBankAccountSetup).toHaveBeenCalledWith(clientSecret, { + payment_method: { + us_bank_account: { + routing_number: "110000000", + account_number: "000123456789", + account_holder_type: "individual", + }, + billing_details: { + name: "John Doe", + address: { + country: "US", + postal_code: "90210", + }, + }, + }, + }); + }); + + it("should omit billing address when not provided", async () => { + mockStripeInstance.confirmUsBankAccountSetup.mockResolvedValue({ + setupIntent: { status: "requires_action", payment_method: "pm_bank_123" }, + }); + + await service.setupBankAccountPaymentMethod(clientSecret, bankAccount); + + const call = mockStripeInstance.confirmUsBankAccountSetup.mock.calls[0][1]; + expect(call.payment_method.billing_details.address).toBeUndefined(); + }); + + it("should validate status is requires_action", async () => { + const error = { message: "Invalid status" }; + mockStripeInstance.confirmUsBankAccountSetup.mockResolvedValue({ + setupIntent: { status: "succeeded" }, + error, + }); + + await expect( + service.setupBankAccountPaymentMethod(clientSecret, bankAccount), + ).rejects.toEqual(error); + }); + + it("should return payment method ID on success", async () => { + mockStripeInstance.confirmUsBankAccountSetup.mockResolvedValue({ + setupIntent: { status: "requires_action", payment_method: "pm_bank_success_456" }, + }); + + const result = await service.setupBankAccountPaymentMethod(clientSecret, bankAccount); + + expect(result).toBe("pm_bank_success_456"); + }); + }); + + describe("unloadStripe - single instance", () => { + const instanceId = "unload-test-instance"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + beforeEach(async () => { + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + }); + + it("should unmount all card elements", () => { + service.unloadStripe(instanceId); + + expect(mockCardNumber.unmount).toHaveBeenCalled(); + expect(mockCardExpiry.unmount).toHaveBeenCalled(); + expect(mockCardCvc.unmount).toHaveBeenCalled(); + }); + + it("should remove instance from Map", () => { + expect(service["instances"].has(instanceId)).toBe(true); + + service.unloadStripe(instanceId); + + expect(service["instances"].has(instanceId)).toBe(false); + }); + + it("should decrement instanceCount", () => { + expect(service["instanceCount"]).toBe(1); + + service.unloadStripe(instanceId); + + expect(service["instanceCount"]).toBe(0); + }); + + it("should remove script when last instance unloaded", () => { + jest.spyOn(window.document, "getElementById").mockReturnValue(mockScript); + + service.unloadStripe(instanceId); + + expect(window.document.head.removeChild).toHaveBeenCalledWith(mockScript); + }); + + it("should remove Stripe iframes after cleanup delay", async () => { + service.unloadStripe(instanceId); + + await advanceTimersAndFlush(500); + + expect(window.document.querySelectorAll).toHaveBeenCalledWith("iframe"); + expect(mockIframe.remove).toHaveBeenCalled(); + }); + }); + + describe("unloadStripe - multiple instances", () => { + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + beforeEach(async () => { + // Load first instance + service.loadStripe("instance-1", elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + // Load second instance (script already loaded) + service.loadStripe("instance-2", elementIds, false); + await advanceTimersAndFlush(100); + }); + + it("should not remove script when other instances exist", () => { + expect(service["instanceCount"]).toBe(2); + + service.unloadStripe("instance-1"); + + expect(service["instanceCount"]).toBe(1); + expect(window.document.head.removeChild).not.toHaveBeenCalled(); + }); + + it("should only cleanup specific instance", () => { + service.unloadStripe("instance-1"); + + expect(service["instances"].has("instance-1")).toBe(false); + expect(service["instances"].has("instance-2")).toBe(true); + }); + + it("should handle reference counting correctly", () => { + expect(service["instanceCount"]).toBe(2); + + service.unloadStripe("instance-1"); + expect(service["instanceCount"]).toBe(1); + + service.unloadStripe("instance-2"); + expect(service["instanceCount"]).toBe(0); + }); + }); + + describe("unloadStripe - edge cases", () => { + it("should handle unload of non-existent instance gracefully", () => { + expect(() => service.unloadStripe("non-existent")).not.toThrow(); + expect(service["instanceCount"]).toBe(0); + }); + + it("should handle duplicate unload calls", async () => { + const instanceId = "duplicate-unload"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + service.unloadStripe(instanceId); + expect(service["instanceCount"]).toBe(0); + + service.unloadStripe(instanceId); + expect(service["instanceCount"]).toBe(0); // Should not go negative + }); + + it("should catch and log element unmount errors", async () => { + const instanceId = "error-unmount"; + const elementIds = { + cardNumber: "#card-number", + cardExpiry: "#card-expiry", + cardCvc: "#card-cvc", + }; + + service.loadStripe(instanceId, elementIds, false); + triggerScriptLoad(); + await advanceTimersAndFlush(100); + + const unmountError = new Error("Unmount failed"); + mockCardNumber.unmount.mockImplementation(() => { + throw unmountError; + }); + + service.unloadStripe(instanceId); + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Error unmounting Stripe elements"), + unmountError, + ); + }); + }); + + describe("element styling", () => { + it("should apply correct CSS custom properties", () => { + const options = service["getElementOptions"]("cardNumber"); + + expect(options.style.base.color).toBe("rgb(0, 0, 0)"); + expect(options.style.base["::placeholder"].color).toBe("rgb(128, 128, 128)"); + expect(options.style.invalid.color).toBe("rgb(0, 0, 0)"); + expect(options.style.invalid.borderColor).toBe("rgb(220, 38, 38)"); + }); + + it("should remove placeholder for cardNumber and cardCvc", () => { + const cardNumberOptions = service["getElementOptions"]("cardNumber"); + const cardCvcOptions = service["getElementOptions"]("cardCvc"); + const cardExpiryOptions = service["getElementOptions"]("cardExpiry"); + + expect(cardNumberOptions.placeholder).toBe(""); + expect(cardCvcOptions.placeholder).toBe(""); + expect(cardExpiryOptions.placeholder).toBeUndefined(); + }); + }); +}); diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index a2eb7cd98f2..9aabab9beb0 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -8,8 +8,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { BankAccountPaymentMethod, CardPaymentMethod } from "../payment/types"; -import { BillingServicesModule } from "./billing-services.module"; - type SetupBankAccountRequest = { payment_method: { us_bank_account: { @@ -39,15 +37,21 @@ type SetupCardRequest = { }; }; -@Injectable({ providedIn: BillingServicesModule }) +@Injectable({ providedIn: "root" }) export class StripeService { - private stripe: any; - private elements: any; - private elementIds: { - cardNumber: string; - cardExpiry: string; - cardCvc: string; - }; + // Shared/Global - One Stripe client for entire application + private stripe: any = null; + private stripeScriptLoaded = false; + private instanceCount = 0; + + // Per-Instance - Isolated Elements for each component + private instances = new Map< + string, + { + elements: any; + elementIds: { cardNumber: string; cardExpiry: string; cardCvc: string }; + } + >(); constructor( private apiService: ApiService, @@ -76,53 +80,121 @@ export class StripeService { * Loads [Stripe JS]{@link https://docs.stripe.com/js} in the <head> element of the current page and mounts * Stripe credit card [elements]{@link https://docs.stripe.com/js/elements_object/create} into the HTML elements with the provided element IDS. * We do this to avoid having to load the Stripe JS SDK on every page of the Web Vault given many pages contain sensitive information. + * @param instanceId - Unique identifier for this component instance. * @param elementIds - The ID attributes of the HTML elements used to load the Stripe JS credit card elements. * @param autoMount - A flag indicating whether you want to immediately mount the Stripe credit card elements. */ loadStripe( + instanceId: string, elementIds: { cardNumber: string; cardExpiry: string; cardCvc: string }, autoMount: boolean, ) { - this.elementIds = elementIds; - const script = window.document.createElement("script"); - script.id = "stripe-script"; - script.src = "https://js.stripe.com/v3?advancedFraudSignals=false"; - script.onload = async () => { - const window$ = window as any; - this.stripe = window$.Stripe(process.env.STRIPE_KEY); - this.elements = this.stripe.elements(); - setTimeout(() => { - this.elements.create("cardNumber", this.getElementOptions("cardNumber")); - this.elements.create("cardExpiry", this.getElementOptions("cardExpiry")); - this.elements.create("cardCvc", this.getElementOptions("cardCvc")); - if (autoMount) { - this.mountElements(); - } - }, 50); - }; + // Check if script is already loaded + if (this.stripeScriptLoaded) { + // Script already loaded, initialize this instance immediately + this.initializeInstance(instanceId, elementIds, autoMount); + } else if (!window.document.getElementById("stripe-script")) { + // Script not loaded and not loading, start loading it + const script = window.document.createElement("script"); + script.id = "stripe-script"; + script.src = "https://js.stripe.com/v3?advancedFraudSignals=false"; + script.onload = async () => { + const window$ = window as any; + this.stripe = window$.Stripe(process.env.STRIPE_KEY); + this.stripeScriptLoaded = true; // Mark as loaded after script loads - window.document.head.appendChild(script); + // Initialize this instance after script loads + this.initializeInstance(instanceId, elementIds, autoMount); + }; + window.document.head.appendChild(script); + } else { + // Script is currently loading, wait for it + this.initializeInstance(instanceId, elementIds, autoMount); + } } - mountElements(attempt: number = 1) { - setTimeout(() => { - if (!this.elements) { - this.logService.warning(`Stripe elements are missing, retrying for attempt ${attempt}...`); - this.mountElements(attempt + 1); + private initializeInstance( + instanceId: string, + elementIds: { cardNumber: string; cardExpiry: string; cardCvc: string }, + autoMount: boolean, + attempt: number = 1, + ) { + // Wait for stripe to be available if script just loaded + if (!this.stripe) { + if (attempt < 10) { + this.logService.warning( + `Stripe not yet loaded for instance ${instanceId}, retrying attempt ${attempt}...`, + ); + setTimeout( + () => this.initializeInstance(instanceId, elementIds, autoMount, attempt + 1), + 50, + ); } else { - const cardNumber = this.elements.getElement("cardNumber"); - const cardExpiry = this.elements.getElement("cardExpiry"); - const cardCVC = this.elements.getElement("cardCvc"); + this.logService.error( + `Stripe failed to load for instance ${instanceId} after ${attempt} attempts`, + ); + } + return; + } + + // Create a new Elements instance for this component + const elements = this.stripe.elements(); + + // Store instance data + this.instances.set(instanceId, { elements, elementIds }); + + // Increment instance count now that instance is successfully initialized + this.instanceCount++; + + // Create the card elements + setTimeout(() => { + elements.create("cardNumber", this.getElementOptions("cardNumber")); + elements.create("cardExpiry", this.getElementOptions("cardExpiry")); + elements.create("cardCvc", this.getElementOptions("cardCvc")); + + if (autoMount) { + this.mountElements(instanceId); + } + }, 50); + } + + mountElements(instanceId: string, attempt: number = 1) { + setTimeout(() => { + const instance = this.instances.get(instanceId); + + if (!instance) { + if (attempt < 10) { + this.logService.warning( + `Stripe instance ${instanceId} not found, retrying for attempt ${attempt}...`, + ); + this.mountElements(instanceId, attempt + 1); + } else { + this.logService.error( + `Stripe instance ${instanceId} not found after ${attempt} attempts`, + ); + } + return; + } + + if (!instance.elements) { + this.logService.warning( + `Stripe elements for instance ${instanceId} are missing, retrying for attempt ${attempt}...`, + ); + this.mountElements(instanceId, attempt + 1); + } else { + const cardNumber = instance.elements.getElement("cardNumber"); + const cardExpiry = instance.elements.getElement("cardExpiry"); + const cardCVC = instance.elements.getElement("cardCvc"); if ([cardNumber, cardExpiry, cardCVC].some((element) => !element)) { this.logService.warning( - `Some Stripe card elements are missing, retrying for attempt ${attempt}...`, + `Some Stripe card elements for instance ${instanceId} are missing, retrying for attempt ${attempt}...`, ); - this.mountElements(attempt + 1); + this.mountElements(instanceId, attempt + 1); } else { - cardNumber.mount(this.elementIds.cardNumber); - cardExpiry.mount(this.elementIds.cardExpiry); - cardCVC.mount(this.elementIds.cardCvc); + cardNumber.mount(instance.elementIds.cardNumber); + cardExpiry.mount(instance.elementIds.cardExpiry); + cardCVC.mount(instance.elementIds.cardCvc); } } }, 100); @@ -132,6 +204,9 @@ export class StripeService { * Creates a Stripe [SetupIntent]{@link https://docs.stripe.com/api/setup_intents} and uses the resulting client secret * to invoke the Stripe JS [confirmUsBankAccountSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_us_bank_account_setup} method, * thereby creating and storing a Stripe [PaymentMethod]{@link https://docs.stripe.com/api/payment_methods}. + * @param clientSecret - The client secret from the SetupIntent. + * @param bankAccount - The bank account details. + * @param billingDetails - Optional billing details. * @returns The ID of the newly created PaymentMethod. */ async setupBankAccountPaymentMethod( @@ -171,13 +246,28 @@ export class StripeService { * Creates a Stripe [SetupIntent]{@link https://docs.stripe.com/api/setup_intents} and uses the resulting client secret * to invoke the Stripe JS [confirmCardSetup]{@link https://docs.stripe.com/js/setup_intents/confirm_card_setup} method, * thereby creating and storing a Stripe [PaymentMethod]{@link https://docs.stripe.com/api/payment_methods}. + * @param instanceId - Unique identifier for the component instance. + * @param clientSecret - The client secret from the SetupIntent. + * @param billingDetails - Optional billing details. * @returns The ID of the newly created PaymentMethod. */ async setupCardPaymentMethod( + instanceId: string, clientSecret: string, billingDetails?: { country: string; postalCode: string }, ): Promise<string> { - const cardNumber = this.elements.getElement("cardNumber"); + const instance = this.instances.get(instanceId); + if (!instance) { + const availableInstances = Array.from(this.instances.keys()); + this.logService.error( + `Stripe instance ${instanceId} not found. ` + + `Available instances: [${availableInstances.join(", ")}]. ` + + `This may occur if the component was destroyed during the payment flow.`, + ); + throw new Error("Payment method initialization failed. Please try again."); + } + + const cardNumber = instance.elements.getElement("cardNumber"); const request: SetupCardRequest = { payment_method: { card: cardNumber, @@ -200,24 +290,77 @@ export class StripeService { } /** - * Removes {@link https://docs.stripe.com/js} from the <head> element of the current page as well as all - * Stripe-managed <iframe> elements. + * Removes the Stripe Elements instance for the specified component. + * Only removes the Stripe script and iframes when the last instance is unloaded. + * @param instanceId - Unique identifier for the component instance to unload. */ - unloadStripe() { - const script = window.document.getElementById("stripe-script"); - window.document.head.removeChild(script); - window.setTimeout(() => { - const iFrames = Array.from(window.document.querySelectorAll("iframe")).filter( - (element) => element.src != null && element.src.indexOf("stripe") > -1, - ); - iFrames.forEach((iFrame) => { - try { - window.document.body.removeChild(iFrame); - } catch (error) { - this.logService.error(error); + unloadStripe(instanceId: string) { + const instance = this.instances.get(instanceId); + + // Only proceed if instance was actually initialized + if (!instance) { + return; + } + + // Unmount all elements for this instance + if (instance.elements) { + try { + const cardNumber = instance.elements.getElement("cardNumber"); + const cardExpiry = instance.elements.getElement("cardExpiry"); + const cardCvc = instance.elements.getElement("cardCvc"); + + if (cardNumber) { + cardNumber.unmount(); } - }); - }, 500); + if (cardExpiry) { + cardExpiry.unmount(); + } + if (cardCvc) { + cardCvc.unmount(); + } + } catch (error) { + this.logService.error( + `Error unmounting Stripe elements for instance ${instanceId}:`, + error, + ); + } + } + + // Remove instance from map + this.instances.delete(instanceId); + + // Decrement instance count (only if instance was initialized) + this.instanceCount--; + + // Only remove script and iframes when no instances remain + if (this.instanceCount <= 0) { + if (this.instanceCount < 0) { + this.logService.error( + `Stripe instance count became negative (${this.instanceCount}). This indicates a reference counting bug.`, + ); + } + this.instanceCount = 0; + this.stripeScriptLoaded = false; + this.stripe = null; + + const script = window.document.getElementById("stripe-script"); + if (script) { + window.document.head.removeChild(script); + } + + window.setTimeout(() => { + const iFrames = Array.from(window.document.querySelectorAll("iframe")).filter( + (element) => element.src != null && element.src.indexOf("stripe") > -1, + ); + iFrames.forEach((iFrame) => { + try { + iFrame.remove(); + } catch (error) { + this.logService.error(error); + } + }); + }, 500); + } } private getElementOptions(element: "cardNumber" | "cardExpiry" | "cardCvc"): any { From 9ee4fd0e44420376411e1ebb70882a8cc6984ff3 Mon Sep 17 00:00:00 2001 From: Matt Andreko <mandreko@bitwarden.com> Date: Wed, 19 Nov 2025 14:15:12 -0500 Subject: [PATCH 183/249] Workflow corrections (#17392) Co-authored-by: Amy Galles <9685081+AmyLGalles@users.noreply.github.com> --- .github/workflows/build-desktop.yml | 2 +- .github/workflows/publish-cli.yml | 2 +- .github/workflows/publish-desktop.yml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 1877374a525..d3566535b65 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -627,7 +627,7 @@ jobs: if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: - name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}..exe + name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 08d3f1de503..426947526a4 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -206,7 +206,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - + - name: Get Node version id: retrieve-node-version working-directory: ./ diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index f42f9811d77..b17312950e9 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -336,7 +336,7 @@ jobs: ruby-version: '3.4.7' bundler-cache: false working-directory: apps/desktop - + - name: Install Fastlane working-directory: apps/desktop run: gem install fastlane @@ -377,15 +377,15 @@ jobs: echo "📦 Publishing build $BUILD_NUMBER to Mac App Store" IS_DRY_RUN="false" fi - + echo "📝 Release notes (${#CHANGELOG} chars): ${CHANGELOG:0:100}..." - + # Validate changelog length (App Store limit is 4000 chars) if [ ${#CHANGELOG} -gt 4000 ]; then echo "❌ Release notes too long: ${#CHANGELOG} characters (max 4000)" exit 1 fi - + fastlane publish --verbose \ app_version:"${_PKG_VERSION}" \ build_number:"$BUILD_NUMBER" \ From e44ab1b411a92d1b3734a2b1af06ed62ec8be7bd Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Wed, 19 Nov 2025 14:57:59 -0500 Subject: [PATCH 184/249] fix: enable dynamic URLs for Chrome web accessible resources (#17429) This commit adds use_dynamic_url: true to the extension's web_accessible_resources configuration. When enabled, Chrome generates random session-based GUIDs for extension resource URLs instead of using the predictable static extension ID. This enhances privacy by making extension resource URLs unpredictable and prevents third-party enumeration of installed extensions. The feature is supported in Chrome 102+ and changes resource URLs from chrome-extension://[static-id]/resource to chrome-extension://[random-guid]/resource, with GUIDs regenerating each browser session while maintaining all existing extension functionality. Addresses: https://bitwarden.atlassian.net/browse/PM-28344 --- apps/browser/src/manifest.v3.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 73403d2321e..2b2aa0f117b 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -164,7 +164,8 @@ "overlay/menu.html", "popup/fonts/*" ], - "matches": ["<all_urls>"] + "matches": ["<all_urls>"], + "use_dynamic_url": true } ], "__firefox__browser_specific_settings": { From 6d1c474fc50165bd316f100d0319629b664ed406 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Wed, 19 Nov 2025 15:13:41 -0500 Subject: [PATCH 185/249] fix: add world: MAIN to Firefox page script registration (#17466) * chore: update @types/firefox-webext-browser * fix: add world: MAIN to Firefox page script registration * review: add world property to registration type --- .../fido2/background/abstractions/fido2.background.ts | 1 + .../autofill/fido2/background/fido2.background.spec.ts | 1 + .../src/autofill/fido2/background/fido2.background.ts | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts b/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts index 6ad069ad56e..b341be28ebb 100644 --- a/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/abstractions/fido2.background.ts @@ -13,6 +13,7 @@ type SharedFido2ScriptRegistrationOptions = SharedFido2ScriptInjectionDetails & matches: string[]; excludeMatches: string[]; allFrames: true; + world?: "MAIN" | "ISOLATED"; }; type Fido2ExtensionMessage = { diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts index 752851b3d37..adb59b8f845 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.spec.ts @@ -203,6 +203,7 @@ describe("Fido2Background", () => { { file: Fido2ContentScript.PageScriptDelayAppend }, { file: Fido2ContentScript.ContentScript }, ], + world: "MAIN", ...sharedRegistrationOptions, }); }); diff --git a/apps/browser/src/autofill/fido2/background/fido2.background.ts b/apps/browser/src/autofill/fido2/background/fido2.background.ts index 22ee4a1822d..a8b016a14d6 100644 --- a/apps/browser/src/autofill/fido2/background/fido2.background.ts +++ b/apps/browser/src/autofill/fido2/background/fido2.background.ts @@ -176,6 +176,7 @@ export class Fido2Background implements Fido2BackgroundInterface { { file: await this.getFido2PageScriptAppendFileName() }, { file: Fido2ContentScript.ContentScript }, ], + world: "MAIN", ...this.sharedRegistrationOptions, }); } diff --git a/package-lock.json b/package-lock.json index 674dcabf122..b017272cd77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -101,7 +101,7 @@ "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", "@types/chrome": "0.1.28", - "@types/firefox-webext-browser": "120.0.4", + "@types/firefox-webext-browser": "143.0.0", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", "@types/jsdom": "21.1.7", @@ -14083,9 +14083,9 @@ "license": "MIT" }, "node_modules/@types/firefox-webext-browser": { - "version": "120.0.4", - "resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-120.0.4.tgz", - "integrity": "sha512-lBrpf08xhiZBigrtdQfUaqX1UauwZ+skbFiL8u2Tdra/rklkKadYmIzTwkNZSWtuZ7OKpFqbE2HHfDoFqvZf6w==", + "version": "143.0.0", + "resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-143.0.0.tgz", + "integrity": "sha512-865dYKMOP0CllFyHmgXV4IQgVL51OSQQCwSoihQ17EwugePKFSAZRc0EI+y7Ly4q7j5KyURlA7LgRpFieO4JOw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 8322d065eba..54e2685bbec 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@storybook/web-components-webpack5": "8.6.12", "@tailwindcss/container-queries": "0.1.1", "@types/chrome": "0.1.28", - "@types/firefox-webext-browser": "120.0.4", + "@types/firefox-webext-browser": "143.0.0", "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", "@types/jsdom": "21.1.7", From d86c918e7161df3cfa27f179394a3f6ad7f2373b Mon Sep 17 00:00:00 2001 From: Andy Pixley <3723676+pixman20@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:11:51 -0500 Subject: [PATCH 186/249] [BRE-1303] Providing method for pinning Chrome extension ID for dev (#17432) --- .github/workflows/build-browser.yml | 17 +++++++++++ apps/browser/package.json | 7 ++++- apps/browser/scripts/update-manifest-dev.sh | 34 +++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100755 apps/browser/scripts/update-manifest-dev.sh diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index dc71d33d605..772cb25d98d 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -218,6 +218,7 @@ jobs: source_archive_name_prefix: "" archive_name_prefix: "" npm_command_prefix: "dist:" + npm_package_dev_prefix: "package:dev:" readable: "open source license" type: "oss" - build_prefix: "bit-" @@ -225,6 +226,7 @@ jobs: source_archive_name_prefix: "bit-" archive_name_prefix: "bit-" npm_command_prefix: "dist:bit:" + npm_package_dev_prefix: "package:bit:dev:" readable: "commercial license" type: "commercial" browser: @@ -232,6 +234,8 @@ jobs: npm_command_suffix: "chrome" archive_name: "dist-chrome.zip" artifact_name: "dist-chrome-MV3" + artifact_name_dev: "dev-chrome-MV3" + archive_name_dev: "dev-chrome.zip" - name: "edge" npm_command_suffix: "edge" archive_name: "dist-edge.zip" @@ -338,6 +342,19 @@ jobs: path: browser-source/apps/browser/dist/${{matrix.license_type.archive_name_prefix}}${{ matrix.browser.archive_name }} if-no-files-found: error + - name: Package dev extension + if: ${{ matrix.browser.archive_name_dev != '' }} + run: npm run ${{ matrix.license_type.npm_package_dev_prefix }}${{ matrix.browser.npm_command_suffix }} + working-directory: browser-source/apps/browser + + - name: Upload dev extension artifact + if: ${{ matrix.browser.archive_name_dev != '' }} + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: ${{ matrix.license_type.artifact_prefix }}${{ matrix.browser.artifact_name_dev }}-${{ env._BUILD_NUMBER }}.zip + path: browser-source/apps/browser/dist/${{matrix.license_type.archive_name_prefix}}${{ matrix.browser.archive_name_dev }} + if-no-files-found: error + build-safari: name: Build Safari - ${{ matrix.license_type.readable }} diff --git a/apps/browser/package.json b/apps/browser/package.json index 72e112e62f7..a6a88b53db0 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -6,6 +6,8 @@ "build:bit": "npm run build:bit:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", + "build:dev:chrome": "npm run build:chrome && npm run update:dev:chrome", + "build:bit:dev:chrome": "npm run build:bit:chrome && npm run update:dev:chrome", "build:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:bit:edge": "cross-env BROWSER=edge MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-browser/webpack.config.js", "build:firefox": "cross-env BROWSER=firefox NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", @@ -55,9 +57,12 @@ "dist:bit:opera:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:opera", "dist:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:safari", "dist:bit:safari:mv3": "cross-env MANIFEST_VERSION=3 npm run dist:bit:safari", + "package:dev:chrome": "npm run update:dev:chrome && ./scripts/compress.sh dev-chrome.zip", + "package:bit:dev:chrome": "npm run update:dev:chrome && ./scripts/compress.sh bit-dev-chrome.zip", "test": "jest", "test:watch": "jest --watch", "test:watch:all": "jest --watchAll", - "test:clearCache": "jest --clear-cache" + "test:clearCache": "jest --clear-cache", + "update:dev:chrome": "./scripts/update-manifest-dev.sh" } } diff --git a/apps/browser/scripts/update-manifest-dev.sh b/apps/browser/scripts/update-manifest-dev.sh new file mode 100755 index 00000000000..2823d4cb510 --- /dev/null +++ b/apps/browser/scripts/update-manifest-dev.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +#### +# Update the manifest key in the build directory. +#### + +set -e +set -u +set -x +set -o pipefail + +SCRIPT_ROOT="$(dirname "$0")" +BUILD_DIR="$SCRIPT_ROOT/../build" + +# Check if build directory exists +if [ -d "$BUILD_DIR" ]; then + cd "$BUILD_DIR" + + # Update manifest with dev public key + MANIFEST_PATH="./manifest.json" + + # Generated arbitrary public key from Chrome Dev Console to pin side-loaded extension IDs during development + DEV_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuIvjtsAVWZM0i5jFhSZcrmwgaf3KWcxM5F16LNDNeivC1EqJ+H5xNZ5R9UN5ueHA2xyyYAOlxY07OcY6CKTGJRJyefbUhszb66sdx26SV5gVkCois99fKBlsbSbd6und/BJYmoFUWvFCNNVH+OxLMqMQWjMMhM2ItLqTYi7dxRE5qd+7LwQpnGG2vTkm/O7nu8U3CtkfcIAGLsiTd7/iuytcMDnC0qFM5tJyY/5I+9QOhpUJ7Ybj3C18BDWDORhqxutWv+MSw//SgUn2/lPQrnrKq7FIVQL7FxxEPqkv4QwFvaixps1cBbMdJ1Ygit1z5JldoSyNxzCa5vVcJLecMQIDAQAB' + + MANIFEST_PATH_TMP="${MANIFEST_PATH}.tmp" + if jq --arg key "$DEV_PUBLIC_KEY" '.key = $key' "$MANIFEST_PATH" > "$MANIFEST_PATH_TMP"; then + mv "$MANIFEST_PATH_TMP" "$MANIFEST_PATH" + echo "Updated manifest key in $MANIFEST_PATH" + else + echo "ERROR: Failed to update manifest with jq" + rm -f "$MANIFEST_PATH_TMP" + exit 1 + fi +fi From 7c4db701b933b91e36d437a6f199e68b1522c367 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik <jprusik@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:14:05 -0500 Subject: [PATCH 187/249] [PM-27797] Prevent host page manipulation of inline menu popover attribute (#17400) * turn off inline experience if host page aggressively competes for top of top-layer * add alert message for top-layer hijack scenarios * widen the backoff threshold * refactor backoff logic to include popover attribute mutations * improve getPageIsOpaque check * do not attempt inline menu insertion if it has been disabled for security concerns * fix typo * cleanup * add tests --- apps/browser/src/_locales/en/messages.json | 3 + ...tofill-inline-menu-content.service.spec.ts | 490 +++++++++++++++++- .../autofill-inline-menu-content.service.ts | 138 ++++- 3 files changed, 611 insertions(+), 20 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index f793b24a0e9..5cc7c30bfb4 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts index f1a74556b24..b7bd24c537b 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.spec.ts @@ -53,13 +53,35 @@ describe("AutofillInlineMenuContentService", () => { }); }); + describe("messageHandlers", () => { + it("returns the extension message handlers", () => { + const handlers = autofillInlineMenuContentService.messageHandlers; + + expect(handlers).toHaveProperty("closeAutofillInlineMenu"); + expect(handlers).toHaveProperty("appendAutofillInlineMenuToDom"); + }); + }); + describe("isElementInlineMenu", () => { - it("returns true if the passed element is the inline menu", () => { + it("returns true if the passed element is the inline menu list", () => { const element = document.createElement("div"); autofillInlineMenuContentService["listElement"] = element; expect(autofillInlineMenuContentService.isElementInlineMenu(element)).toBe(true); }); + + it("returns true if the passed element is the inline menu button", () => { + const element = document.createElement("div"); + autofillInlineMenuContentService["buttonElement"] = element; + + expect(autofillInlineMenuContentService.isElementInlineMenu(element)).toBe(true); + }); + + it("returns false if the passed element is not the inline menu", () => { + const element = document.createElement("div"); + + expect(autofillInlineMenuContentService.isElementInlineMenu(element)).toBe(false); + }); }); describe("extension message handlers", () => { @@ -388,7 +410,7 @@ describe("AutofillInlineMenuContentService", () => { }); it("closes the inline menu if the page body is not sufficiently opaque", async () => { - document.querySelector("html").style.opacity = "0.9"; + document.documentElement.style.opacity = "0.9"; document.body.style.opacity = "0"; await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); @@ -397,7 +419,7 @@ describe("AutofillInlineMenuContentService", () => { }); it("closes the inline menu if the page html is not sufficiently opaque", async () => { - document.querySelector("html").style.opacity = "0.3"; + document.documentElement.style.opacity = "0.3"; document.body.style.opacity = "0.7"; await autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]); @@ -406,7 +428,7 @@ describe("AutofillInlineMenuContentService", () => { }); it("does not close the inline menu if the page html and body is sufficiently opaque", async () => { - document.querySelector("html").style.opacity = "0.9"; + document.documentElement.style.opacity = "0.9"; document.body.style.opacity = "1"; await autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]); await waitForIdleCallback(); @@ -599,5 +621,465 @@ describe("AutofillInlineMenuContentService", () => { overlayElement: AutofillOverlayElement.List, }); }); + + it("clears the persistent last child override timeout", () => { + jest.useFakeTimers(); + const clearTimeoutSpy = jest.spyOn(globalThis, "clearTimeout"); + autofillInlineMenuContentService["handlePersistentLastChildOverrideTimeout"] = setTimeout( + jest.fn(), + 500, + ); + + autofillInlineMenuContentService.destroy(); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + }); + + it("unobserves page attributes", () => { + const disconnectSpy = jest.spyOn( + autofillInlineMenuContentService["htmlMutationObserver"], + "disconnect", + ); + + autofillInlineMenuContentService.destroy(); + + expect(disconnectSpy).toHaveBeenCalled(); + }); + }); + + describe("getOwnedTagNames", () => { + it("returns an empty array when no elements are created", () => { + expect(autofillInlineMenuContentService.getOwnedTagNames()).toEqual([]); + }); + + it("returns the button element tag name", () => { + const buttonElement = document.createElement("div"); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + + const tagNames = autofillInlineMenuContentService.getOwnedTagNames(); + + expect(tagNames).toContain("DIV"); + }); + + it("returns both button and list element tag names", () => { + const buttonElement = document.createElement("div"); + const listElement = document.createElement("span"); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + autofillInlineMenuContentService["listElement"] = listElement; + + const tagNames = autofillInlineMenuContentService.getOwnedTagNames(); + + expect(tagNames).toEqual(["DIV", "SPAN"]); + }); + }); + + describe("getUnownedTopLayerItems", () => { + beforeEach(() => { + document.body.innerHTML = ""; + }); + + it("returns the tag names from button and list elements", () => { + const buttonElement = document.createElement("div"); + buttonElement.setAttribute("popover", "manual"); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + + const listElement = document.createElement("span"); + listElement.setAttribute("popover", "manual"); + autofillInlineMenuContentService["listElement"] = listElement; + + /** Mock querySelectorAll to avoid :modal selector issues in jsdom */ + const querySelectorAllSpy = jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([] as any); + + const items = autofillInlineMenuContentService.getUnownedTopLayerItems(); + + expect(querySelectorAllSpy).toHaveBeenCalled(); + expect(items.length).toBe(0); + }); + + it("calls querySelectorAll with correct selector when includeCandidates is false", () => { + /** Mock querySelectorAll to avoid :modal selector issues in jsdom */ + const querySelectorAllSpy = jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([] as any); + + autofillInlineMenuContentService.getUnownedTopLayerItems(false); + + const calledSelector = querySelectorAllSpy.mock.calls[0][0]; + expect(calledSelector).toContain(":modal"); + expect(calledSelector).toContain(":popover-open"); + }); + + it("includes candidates selector when requested", () => { + /** Mock querySelectorAll to avoid :modal selector issues in jsdom */ + const querySelectorAllSpy = jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([] as any); + + autofillInlineMenuContentService.getUnownedTopLayerItems(true); + + const calledSelector = querySelectorAllSpy.mock.calls[0][0]; + expect(calledSelector).toContain("[popover], dialog"); + }); + }); + + describe("refreshTopLayerPosition", () => { + it("does nothing when inline menu is disabled", () => { + const getUnownedTopLayerItemsSpy = jest.spyOn( + autofillInlineMenuContentService, + "getUnownedTopLayerItems", + ); + + autofillInlineMenuContentService["inlineMenuEnabled"] = false; + const buttonElement = document.createElement("div"); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + + autofillInlineMenuContentService.refreshTopLayerPosition(); + + // Should exit early and not call `getUnownedTopLayerItems` + expect(getUnownedTopLayerItemsSpy).not.toHaveBeenCalled(); + }); + + it("does nothing when no other top layer items exist", () => { + const buttonElement = document.createElement("div"); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + jest + .spyOn(autofillInlineMenuContentService, "getUnownedTopLayerItems") + .mockReturnValue([] as any); + + const getElementsByTagSpy = jest.spyOn(globalThis.document, "getElementsByTagName"); + + autofillInlineMenuContentService.refreshTopLayerPosition(); + + // Should exit early and not get inline elements to refresh + expect(getElementsByTagSpy).not.toHaveBeenCalled(); + }); + + it("refreshes button popover when button is in document", () => { + jest + .spyOn(autofillInlineMenuContentService, "getUnownedTopLayerItems") + .mockReturnValue([document.createElement("div")] as any); + + const buttonElement = document.createElement("div"); + buttonElement.setAttribute("popover", "manual"); + buttonElement.showPopover = jest.fn(); + buttonElement.hidePopover = jest.fn(); + document.body.appendChild(buttonElement); + autofillInlineMenuContentService["buttonElement"] = buttonElement; + + autofillInlineMenuContentService.refreshTopLayerPosition(); + + expect(buttonElement.hidePopover).toHaveBeenCalled(); + expect(buttonElement.showPopover).toHaveBeenCalled(); + }); + + it("refreshes list popover when list is in document", () => { + jest + .spyOn(autofillInlineMenuContentService, "getUnownedTopLayerItems") + .mockReturnValue([document.createElement("div")] as any); + + const listElement = document.createElement("div"); + listElement.setAttribute("popover", "manual"); + listElement.showPopover = jest.fn(); + listElement.hidePopover = jest.fn(); + document.body.appendChild(listElement); + autofillInlineMenuContentService["listElement"] = listElement; + + autofillInlineMenuContentService.refreshTopLayerPosition(); + + expect(listElement.hidePopover).toHaveBeenCalled(); + expect(listElement.showPopover).toHaveBeenCalled(); + }); + }); + + describe("checkAndUpdateRefreshCount", () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date("2023-01-01T00:00:00.000Z")); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("does nothing when inline menu is disabled", () => { + autofillInlineMenuContentService["inlineMenuEnabled"] = false; + + autofillInlineMenuContentService["checkAndUpdateRefreshCount"]("topLayer"); + + expect(autofillInlineMenuContentService["refreshCountWithinTimeThreshold"].topLayer).toBe(0); + }); + + it("increments refresh count when within time threshold", () => { + autofillInlineMenuContentService["lastTrackedTimestamp"].topLayer = Date.now() - 1000; + + autofillInlineMenuContentService["checkAndUpdateRefreshCount"]("topLayer"); + + expect(autofillInlineMenuContentService["refreshCountWithinTimeThreshold"].topLayer).toBe(1); + }); + + it("resets count when outside time threshold", () => { + autofillInlineMenuContentService["lastTrackedTimestamp"].topLayer = Date.now() - 6000; + autofillInlineMenuContentService["refreshCountWithinTimeThreshold"].topLayer = 5; + + autofillInlineMenuContentService["checkAndUpdateRefreshCount"]("topLayer"); + + expect(autofillInlineMenuContentService["refreshCountWithinTimeThreshold"].topLayer).toBe(0); + }); + + it("disables inline menu and shows alert when count exceeds threshold", () => { + const alertSpy = jest.spyOn(globalThis.window, "alert").mockImplementation(); + const checkPageRisksSpy = jest.spyOn( + autofillInlineMenuContentService as any, + "checkPageRisks", + ); + autofillInlineMenuContentService["lastTrackedTimestamp"].topLayer = Date.now() - 1000; + autofillInlineMenuContentService["refreshCountWithinTimeThreshold"].topLayer = 6; + + autofillInlineMenuContentService["checkAndUpdateRefreshCount"]("topLayer"); + + expect(autofillInlineMenuContentService["inlineMenuEnabled"]).toBe(false); + expect(alertSpy).toHaveBeenCalled(); + expect(checkPageRisksSpy).toHaveBeenCalled(); + }); + }); + + describe("refreshPopoverAttribute", () => { + it("calls checkAndUpdateRefreshCount with popoverAttribute type", () => { + const checkSpy = jest.spyOn( + autofillInlineMenuContentService as any, + "checkAndUpdateRefreshCount", + ); + const element = document.createElement("div"); + element.setAttribute("popover", "auto"); + element.showPopover = jest.fn(); + + autofillInlineMenuContentService["refreshPopoverAttribute"](element); + + expect(checkSpy).toHaveBeenCalledWith("popoverAttribute"); + expect(element.getAttribute("popover")).toBe("manual"); + expect(element.showPopover).toHaveBeenCalled(); + }); + }); + + describe("handleInlineMenuElementMutationObserverUpdate - popover attribute", () => { + it("refreshes popover attribute when changed from manual", () => { + const element = document.createElement("div"); + element.setAttribute("popover", "auto"); + element.showPopover = jest.fn(); + const refreshSpy = jest.spyOn( + autofillInlineMenuContentService as any, + "refreshPopoverAttribute", + ); + autofillInlineMenuContentService["buttonElement"] = element; + + const mockMutation = createMutationRecordMock({ + target: element, + type: "attributes", + attributeName: "popover", + }); + + autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([ + mockMutation, + ]); + + expect(refreshSpy).toHaveBeenCalledWith(element); + }); + + it("does not refresh popover attribute when already manual", () => { + const element = document.createElement("div"); + element.setAttribute("popover", "manual"); + const refreshSpy = jest.spyOn( + autofillInlineMenuContentService as any, + "refreshPopoverAttribute", + ); + autofillInlineMenuContentService["buttonElement"] = element; + + const mockMutation = createMutationRecordMock({ + target: element, + type: "attributes", + attributeName: "popover", + }); + + autofillInlineMenuContentService["handleInlineMenuElementMutationObserverUpdate"]([ + mockMutation, + ]); + + expect(refreshSpy).not.toHaveBeenCalled(); + }); + }); + + describe("appendInlineMenuElements when disabled", () => { + beforeEach(() => { + observeContainerMutationsSpy.mockImplementation(); + }); + + it("does not append button when inline menu is disabled", async () => { + autofillInlineMenuContentService["inlineMenuEnabled"] = false; + jest.spyOn(globalThis.document.body, "appendChild"); + + sendMockExtensionMessage({ + command: "appendAutofillInlineMenuToDom", + overlayElement: AutofillOverlayElement.Button, + }); + await flushPromises(); + + expect(globalThis.document.body.appendChild).not.toHaveBeenCalled(); + }); + + it("does not append list when inline menu is disabled", async () => { + autofillInlineMenuContentService["inlineMenuEnabled"] = false; + jest.spyOn(globalThis.document.body, "appendChild"); + + sendMockExtensionMessage({ + command: "appendAutofillInlineMenuToDom", + overlayElement: AutofillOverlayElement.List, + }); + await flushPromises(); + + expect(globalThis.document.body.appendChild).not.toHaveBeenCalled(); + }); + }); + + describe("custom element creation for non-Firefox browsers", () => { + beforeEach(() => { + autofillInlineMenuContentService["isFirefoxBrowser"] = false; + observeContainerMutationsSpy.mockImplementation(); + }); + + it("creates a custom element for button in non-Firefox browsers", () => { + const definespy = jest.spyOn(globalThis.customElements, "define"); + + sendMockExtensionMessage({ + command: "appendAutofillInlineMenuToDom", + overlayElement: AutofillOverlayElement.Button, + }); + + expect(definespy).toHaveBeenCalled(); + expect(autofillInlineMenuContentService["buttonElement"]).toBeDefined(); + expect(autofillInlineMenuContentService["buttonElement"]?.tagName).not.toBe("DIV"); + }); + + it("creates a custom element for list in non-Firefox browsers", () => { + const defineSpy = jest.spyOn(globalThis.customElements, "define"); + + sendMockExtensionMessage({ + command: "appendAutofillInlineMenuToDom", + overlayElement: AutofillOverlayElement.List, + }); + + expect(defineSpy).toHaveBeenCalled(); + expect(autofillInlineMenuContentService["listElement"]).toBeDefined(); + expect(autofillInlineMenuContentService["listElement"]?.tagName).not.toBe("DIV"); + }); + }); + + describe("getPageIsOpaque", () => { + it("returns false when no page elements exist", () => { + jest.spyOn(globalThis.document, "querySelectorAll").mockReturnValue([] as any); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(false); + }); + + it("returns true when all html and body nodes have sufficient opacity", () => { + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body] as any); + jest + .spyOn(globalThis.window, "getComputedStyle") + .mockImplementation(() => ({ opacity: "1" }) as CSSStyleDeclaration); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(true); + }); + + it("returns false when html opacity is below threshold", () => { + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body] as any); + let callCount = 0; + jest.spyOn(globalThis.window, "getComputedStyle").mockImplementation(() => { + callCount++; + return { opacity: callCount === 1 ? "0.5" : "1" } as CSSStyleDeclaration; + }); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(false); + }); + + it("returns false when body opacity is below threshold", () => { + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body] as any); + let callCount = 0; + jest.spyOn(globalThis.window, "getComputedStyle").mockImplementation(() => { + callCount++; + return { opacity: callCount === 1 ? "1" : "0.5" } as CSSStyleDeclaration; + }); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(false); + }); + + it("returns false when opacity of at least one duplicate body is below threshold", () => { + const duplicateBody = document.createElement("body"); + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body, duplicateBody] as any); + let callCount = 0; + jest.spyOn(globalThis.window, "getComputedStyle").mockImplementation(() => { + callCount++; + + let opacityValue = "0.5"; + switch (callCount) { + case 1: + opacityValue = "1"; + break; + case 2: + opacityValue = "0.7"; + break; + default: + break; + } + + return { opacity: opacityValue } as CSSStyleDeclaration; + }); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(false); + }); + + it("returns true when opacity is above threshold", () => { + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body] as any); + jest + .spyOn(globalThis.window, "getComputedStyle") + .mockImplementation(() => ({ opacity: "0.7" }) as CSSStyleDeclaration); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(true); + }); + + it("returns false when opacity is at threshold", () => { + jest + .spyOn(globalThis.document, "querySelectorAll") + .mockReturnValue([document.documentElement, document.body] as any); + jest + .spyOn(globalThis.window, "getComputedStyle") + .mockImplementation(() => ({ opacity: "0.6" }) as CSSStyleDeclaration); + + const result = autofillInlineMenuContentService["getPageIsOpaque"](); + + expect(result).toBe(false); + }); }); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts index be93e863275..b61e5e19d53 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/content/autofill-inline-menu-content.service.ts @@ -22,6 +22,19 @@ import { import { AutofillInlineMenuButtonIframe } from "../iframe-content/autofill-inline-menu-button-iframe"; import { AutofillInlineMenuListIframe } from "../iframe-content/autofill-inline-menu-list-iframe"; +const experienceValidationBackoffThresholds = { + topLayer: { + countLimit: 5, + timeSpanLimit: 5000, + }, + popoverAttribute: { + countLimit: 10, + timeSpanLimit: 5000, + }, +}; + +type BackoffCheckType = keyof typeof experienceValidationBackoffThresholds; + export class AutofillInlineMenuContentService implements AutofillInlineMenuContentServiceInterface { private readonly sendExtensionMessage = sendExtensionMessage; private readonly generateRandomCustomElementName = generateRandomCustomElementName; @@ -35,6 +48,19 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte private bodyMutationObserver: MutationObserver; private inlineMenuElementsMutationObserver: MutationObserver; private containerElementMutationObserver: MutationObserver; + private refreshCountWithinTimeThreshold: { [key in BackoffCheckType]: number } = { + topLayer: 0, + popoverAttribute: 0, + }; + private lastTrackedTimestamp = { + topLayer: Date.now(), + popoverAttribute: Date.now(), + }; + /** + * Distinct from preventing inline menu script injection, this is for cases + * where the page is subsequently determined to be risky. + */ + private inlineMenuEnabled = true; private mutationObserverIterations = 0; private mutationObserverIterationsResetTimeout: number | NodeJS.Timeout; private handlePersistentLastChildOverrideTimeout: number | NodeJS.Timeout; @@ -140,6 +166,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * Updates the position of both the inline menu button and inline menu list. */ private async appendInlineMenuElements({ overlayElement }: AutofillExtensionMessage) { + if (!this.inlineMenuEnabled) { + return; + } + if (overlayElement === AutofillOverlayElement.Button) { return this.appendButtonElement(); } @@ -151,6 +181,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * Updates the position of the inline menu button. */ private async appendButtonElement(): Promise<void> { + if (!this.inlineMenuEnabled) { + return; + } + if (!this.buttonElement) { this.createButtonElement(); this.updateCustomElementDefaultStyles(this.buttonElement); @@ -167,6 +201,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * Updates the position of the inline menu list. */ private async appendListElement(): Promise<void> { + if (!this.inlineMenuEnabled) { + return; + } + if (!this.listElement) { this.createListElement(); this.updateCustomElementDefaultStyles(this.listElement); @@ -219,6 +257,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * to create the element if it already exists in the DOM. */ private createButtonElement() { + if (!this.inlineMenuEnabled) { + return; + } + if (this.isFirefoxBrowser) { this.buttonElement = globalThis.document.createElement("div"); this.buttonElement.setAttribute("popover", "manual"); @@ -247,6 +289,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * to create the element if it already exists in the DOM. */ private createListElement() { + if (!this.inlineMenuEnabled) { + return; + } + if (this.isFirefoxBrowser) { this.listElement = globalThis.document.createElement("div"); this.listElement.setAttribute("popover", "manual"); @@ -381,14 +427,23 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte } const element = record.target as HTMLElement; - if (record.attributeName !== "style") { - this.removeModifiedElementAttributes(element); + if (record.attributeName === "popover" && this.inlineMenuEnabled) { + const attributeValue = element.getAttribute(record.attributeName); + if (attributeValue !== "manual") { + this.refreshPopoverAttribute(element); + } continue; } - element.removeAttribute("style"); - this.updateCustomElementDefaultStyles(element); + if (record.attributeName === "style") { + element.removeAttribute("style"); + this.updateCustomElementDefaultStyles(element); + + continue; + } + + this.removeModifiedElementAttributes(element); } }; @@ -402,7 +457,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte const attributes = Array.from(element.attributes); for (let attributeIndex = 0; attributeIndex < attributes.length; attributeIndex++) { const attribute = attributes[attributeIndex]; - if (attribute.name === "style") { + if (attribute.name === "style" || attribute.name === "popover") { continue; } @@ -432,7 +487,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte private checkPageRisks = async () => { const pageIsOpaque = await this.getPageIsOpaque(); - const risksFound = !pageIsOpaque; + const risksFound = !pageIsOpaque || !this.inlineMenuEnabled; if (risksFound) { this.closeInlineMenu(); @@ -483,7 +538,49 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte return otherTopLayeritems; }; + /** + * Internally track owned injected experience refreshes as a side-effect + * of host page interference. + */ + private checkAndUpdateRefreshCount = (countType: BackoffCheckType) => { + if (!this.inlineMenuEnabled) { + return; + } + + const { countLimit, timeSpanLimit } = experienceValidationBackoffThresholds[countType]; + const now = Date.now(); + const timeSinceLastTrackedRefresh = now - this.lastTrackedTimestamp[countType]; + const currentlyWithinTimeThreshold = timeSinceLastTrackedRefresh <= timeSpanLimit; + const withinCountThreshold = this.refreshCountWithinTimeThreshold[countType] <= countLimit; + + if (currentlyWithinTimeThreshold) { + if (withinCountThreshold) { + this.refreshCountWithinTimeThreshold[countType]++; + } else { + // Set inline menu to be off; page is aggressively trying to take top position of top layer + this.inlineMenuEnabled = false; + void this.checkPageRisks(); + + const warningMessage = chrome.i18n.getMessage("topLayerHijackWarning"); + globalThis.window.alert(warningMessage); + } + } else { + this.lastTrackedTimestamp[countType] = now; + this.refreshCountWithinTimeThreshold[countType] = 0; + } + }; + + private refreshPopoverAttribute = (element: HTMLElement) => { + this.checkAndUpdateRefreshCount("popoverAttribute"); + element.setAttribute("popover", "manual"); + element.showPopover(); + }; + refreshTopLayerPosition = () => { + if (!this.inlineMenuEnabled) { + return; + } + const otherTopLayerItems = this.getUnownedTopLayerItems(); // No need to refresh if there are no other top-layer items @@ -497,6 +594,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte const listInDocument = this.listElement && (globalThis.document.getElementsByTagName(this.listElement.tagName)[0] as HTMLElement); + if (buttonInDocument) { buttonInDocument.hidePopover(); buttonInDocument.showPopover(); @@ -506,6 +604,10 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte listInDocument.hidePopover(); listInDocument.showPopover(); } + + if (buttonInDocument || listInDocument) { + this.checkAndUpdateRefreshCount("topLayer"); + } }; /** @@ -515,24 +617,28 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte * `body` (enforced elsewhere). */ private getPageIsOpaque = () => { - // These are computed style values, so we don't need to worry about non-float values - // for `opacity`, here // @TODO for definitive checks, traverse up the node tree from the inline menu container; // nodes can exist between `html` and `body` - const htmlElement = globalThis.document.querySelector("html"); - const bodyElement = globalThis.document.querySelector("body"); + /** + * `querySelectorAll` for (non-standard) cases where the page has additional copies of + * page nodes that should be unique + */ + const pageElements = globalThis.document.querySelectorAll("html, body"); - if (!htmlElement || !bodyElement) { + if (!pageElements.length) { return false; } - const htmlOpacity = globalThis.window.getComputedStyle(htmlElement)?.opacity || "0"; - const bodyOpacity = globalThis.window.getComputedStyle(bodyElement)?.opacity || "0"; + return [...pageElements].every((element) => { + // These are computed style values, so we don't need to worry about non-float values + // for `opacity`, here + const elementOpacity = globalThis.window.getComputedStyle(element)?.opacity || "0"; - // Any value above this is considered "opaque" for our purposes - const opacityThreshold = 0.6; + // Any value above this is considered "opaque" for our purposes + const opacityThreshold = 0.6; - return parseFloat(htmlOpacity) > opacityThreshold && parseFloat(bodyOpacity) > opacityThreshold; + return parseFloat(elementOpacity) > opacityThreshold; + }); }; /** From 5f27452ac22b2b70faa81892c103561d1db61da6 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Thu, 20 Nov 2025 02:41:59 +0100 Subject: [PATCH 188/249] Fix desktop not launching (#17485) --- apps/desktop/src/platform/popup-modal-styles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/platform/popup-modal-styles.ts b/apps/desktop/src/platform/popup-modal-styles.ts index ae46ebb5c76..5c5619bd463 100644 --- a/apps/desktop/src/platform/popup-modal-styles.ts +++ b/apps/desktop/src/platform/popup-modal-styles.ts @@ -45,11 +45,11 @@ export function applyMainWindowStyles(window: BrowserWindow, existingWindowState // need to guard against null/undefined values if (existingWindowState?.width && existingWindowState?.height) { - window.setSize(existingWindowState.width, existingWindowState.height); + window.setSize(Math.floor(existingWindowState.width), Math.floor(existingWindowState.height)); } if (existingWindowState?.x && existingWindowState?.y) { - window.setPosition(existingWindowState.x, existingWindowState.y); + window.setPosition(Math.floor(existingWindowState.x), Math.floor(existingWindowState.y)); } window.setWindowButtonVisibility?.(true); From 9e6d0cce35196a53c9305820333dc701207d9280 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:00:18 -0800 Subject: [PATCH 189/249] feat(marketing-initiated-premium): Auth [PM-27542] Write fromMarketing value to state (#17470) --- .../registration-finish.component.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index 7e7b9131fac..19e7c1feabd 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -5,6 +5,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Params, Router, RouterModule } from "@angular/router"; import { Subject, firstValueFrom } from "rxjs"; +import { PremiumInterestStateService } from "@bitwarden/angular/billing/services/premium-interest/premium-interest-state.service.abstraction"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service"; @@ -31,6 +32,12 @@ import { PasswordInputResult } from "../../input-password/password-input-result" import { RegistrationFinishService } from "./registration-finish.service"; +const MarketingInitiative = Object.freeze({ + Premium: "premium", +} as const); + +type MarketingInitiative = (typeof MarketingInitiative)[keyof typeof MarketingInitiative]; + // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -46,6 +53,12 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { submitting = false; email: string; + /** + * Indicates that the user is coming from a marketing page designed to streamline + * users who intend to setup a premium subscription after registration. + */ + premiumInterest = false; + // Note: this token is the email verification token. When it is supplied as a query param, // it either comes from the email verification email or, if email verification is disabled server side // via global settings, it comes directly from the registration-start component directly. @@ -79,6 +92,7 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { private logService: LogService, private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, private loginSuccessHandlerService: LoginSuccessHandlerService, + private premiumInterestStateService: PremiumInterestStateService, ) {} async ngOnInit() { @@ -126,6 +140,10 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { this.providerInviteToken = qParams.providerInviteToken; this.providerUserId = qParams.providerUserId; } + + if (qParams.fromMarketing != null && qParams.fromMarketing === MarketingInitiative.Premium) { + this.premiumInterest = true; + } } private async initOrgInviteFlowIfPresent(): Promise<boolean> { @@ -190,6 +208,13 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { await this.loginSuccessHandlerService.run(authenticationResult.userId); + if (this.premiumInterest) { + await this.premiumInterestStateService.setPremiumInterest( + authenticationResult.userId, + true, + ); + } + await this.router.navigate(["/vault"]); } catch (e) { // If login errors, redirect to login page per product. Don't show error From b00987180d8702ecf7908470a538e83cfcd00ce1 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:26:47 -0600 Subject: [PATCH 190/249] [PM-26688][PM-27710] Delay skeletons from showing + search (#17394) * add custom operator for loading skeleton delays * add `isCipherSearching$` observable to search service * prevent vault skeleton from showing immediately * add skeleton for search + delay to sends * update fade-in-out component selector * add fade-in-out component for generic use * address memory leak by using defer to encapsulate `skeletonShownAt` * add missing provider --- .../popup/send-v2/send-v2.component.html | 4 +- .../tools/popup/send-v2/send-v2.component.ts | 13 ++- .../vault-fade-in-out.component.html | 1 + .../vault-fade-in-out.component.ts | 20 ++++ .../vault-v2/vault-v2.component.html | 82 ++++++++----- .../vault-v2/vault-v2.component.spec.ts | 5 + .../components/vault-v2/vault-v2.component.ts | 22 +++- .../src/vault/abstractions/search.service.ts | 3 + .../src/vault/services/search.service.ts | 15 ++- .../utils/skeleton-loading.operator.spec.ts | 109 ++++++++++++++++++ .../vault/utils/skeleton-loading.operator.ts | 59 ++++++++++ 11 files changed, 295 insertions(+), 38 deletions(-) create mode 100644 apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.html create mode 100644 apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.ts create mode 100644 libs/common/src/vault/utils/skeleton-loading.operator.spec.ts create mode 100644 libs/common/src/vault/utils/skeleton-loading.operator.ts diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.html b/apps/browser/src/tools/popup/send-v2/send-v2.component.html index 0bcbd47a145..47ecd7564dc 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.html +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.html @@ -47,8 +47,8 @@ <app-send-list-items-container [headerText]="title | i18n" [sends]="sends$ | async" /> </ng-container> @if (showSkeletonsLoaders$ | async) { - <vault-fade-in-skeleton> + <vault-fade-in-out-skeleton> <vault-loading-skeleton></vault-loading-skeleton> - </vault-fade-in-skeleton> + </vault-fade-in-out-skeleton> } </popup-page> diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 43a1119deca..e3baba53c42 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -15,6 +15,8 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; +import { skeletonLoadingDelay } from "@bitwarden/common/vault/utils/skeleton-loading.operator"; import { ButtonModule, CalloutModule, @@ -95,8 +97,16 @@ export class SendV2Component implements OnDestroy { /** Skeleton Loading State */ protected showSkeletonsLoaders$ = combineLatest([ this.sendsLoading$, + this.searchService.isSendSearching$, this.skeletonFeatureFlag$, - ]).pipe(map(([loading, skeletonsEnabled]) => loading && skeletonsEnabled)); + ]).pipe( + map( + ([loading, cipherSearching, skeletonsEnabled]) => + (loading || cipherSearching) && skeletonsEnabled, + ), + distinctUntilChanged(), + skeletonLoadingDelay(), + ); protected title: string = "allSends"; protected noItemIcon = NoSendsIcon; @@ -110,6 +120,7 @@ export class SendV2Component implements OnDestroy { private policyService: PolicyService, private accountService: AccountService, private configService: ConfigService, + private searchService: SearchService, ) { combineLatest([ this.sendItemsService.emptyList$, diff --git a/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.html b/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.html new file mode 100644 index 00000000000..6dbc7430638 --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.html @@ -0,0 +1 @@ +<ng-content></ng-content> diff --git a/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.ts b/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.ts new file mode 100644 index 00000000000..a30a447833b --- /dev/null +++ b/apps/browser/src/vault/popup/components/vault-fade-in-out/vault-fade-in-out.component.ts @@ -0,0 +1,20 @@ +import { animate, style, transition, trigger } from "@angular/animations"; +import { ChangeDetectionStrategy, Component, HostBinding } from "@angular/core"; + +@Component({ + selector: "vault-fade-in-out", + templateUrl: "./vault-fade-in-out.component.html", + animations: [ + trigger("fadeInOut", [ + transition(":enter", [ + style({ opacity: 0 }), + animate("100ms ease-in", style({ opacity: 1 })), + ]), + transition(":leave", [animate("300ms ease-out", style({ opacity: 0 }))]), + ]), + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class VaultFadeInOutComponent { + @HostBinding("@fadeInOut") fadeInOut = true; +} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index faaa6a40e98..7a5a99c8100 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -8,20 +8,32 @@ </ng-container> </popup-header> - <div - *ngIf="vaultState === VaultStateEnum.Empty" - class="tw-flex tw-flex-col tw-h-full tw-justify-center" - > - <bit-no-items [icon]="vaultIcon"> - <ng-container slot="title">{{ "yourVaultIsEmpty" | i18n }}</ng-container> - <ng-container slot="description"> - <p bitTypography="body2" class="tw-mx-6 tw-mt-2">{{ "emptyVaultDescription" | i18n }}</p> - </ng-container> - <a slot="button" bitButton buttonType="secondary" [routerLink]="['/add-cipher']"> - {{ "newLogin" | i18n }} - </a> - </bit-no-items> - </div> + <ng-template #emptyVaultTemplate> + <div + *ngIf="vaultState === VaultStateEnum.Empty" + class="tw-flex tw-flex-col tw-h-full tw-justify-center" + > + <bit-no-items [icon]="vaultIcon"> + <ng-container slot="title">{{ "yourVaultIsEmpty" | i18n }}</ng-container> + <ng-container slot="description"> + <p bitTypography="body2" class="tw-mx-6 tw-mt-2"> + {{ "emptyVaultDescription" | i18n }} + </p> + </ng-container> + <a slot="button" bitButton buttonType="secondary" [routerLink]="['/add-cipher']"> + {{ "newLogin" | i18n }} + </a> + </bit-no-items> + </div> + </ng-template> + + @if (skeletonFeatureFlag$ | async) { + <vault-fade-in-out *ngIf="vaultState === VaultStateEnum.Empty"> + <ng-container *ngTemplateOutlet="emptyVaultTemplate"></ng-container> + </vault-fade-in-out> + } @else { + <ng-container *ngTemplateOutlet="emptyVaultTemplate"></ng-container> + } <blocked-injection-banner *ngIf="vaultState !== VaultStateEnum.Empty" @@ -95,22 +107,32 @@ </div> </div> - <ng-container *ngIf="vaultState === null"> - <app-autofill-vault-list-items></app-autofill-vault-list-items> - <app-vault-list-items-container - [title]="'favorites' | i18n" - [ciphers]="(favoriteCiphers$ | async) || []" - id="favorites" - collapsibleKey="favorites" - ></app-vault-list-items-container> - <app-vault-list-items-container - [title]="'allItems' | i18n" - [ciphers]="(remainingCiphers$ | async) || []" - id="allItems" - disableSectionMargin - collapsibleKey="allItems" - ></app-vault-list-items-container> - </ng-container> + <ng-template #vaultContentTemplate> + <ng-container *ngIf="vaultState === null"> + <app-autofill-vault-list-items></app-autofill-vault-list-items> + <app-vault-list-items-container + [title]="'favorites' | i18n" + [ciphers]="(favoriteCiphers$ | async) || []" + id="favorites" + collapsibleKey="favorites" + ></app-vault-list-items-container> + <app-vault-list-items-container + [title]="'allItems' | i18n" + [ciphers]="(remainingCiphers$ | async) || []" + id="allItems" + disableSectionMargin + collapsibleKey="allItems" + ></app-vault-list-items-container> + </ng-container> + </ng-template> + + @if (skeletonFeatureFlag$ | async) { + <vault-fade-in-out *ngIf="vaultState === null"> + <ng-container *ngTemplateOutlet="vaultContentTemplate"></ng-container> + </vault-fade-in-out> + } @else { + <ng-container *ngTemplateOutlet="vaultContentTemplate"></ng-container> + } </ng-container> @if (showSkeletonsLoaders$ | async) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts index 563ec5f9709..5563cd3033b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.spec.ts @@ -23,6 +23,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; import { TaskService } from "@bitwarden/common/vault/tasks"; import { DialogService } from "@bitwarden/components"; @@ -259,6 +260,10 @@ describe("VaultV2Component", () => { getFeatureFlag$: (_: string) => of(false), }, }, + { + provide: SearchService, + useValue: { isCipherSearching$: of(false) }, + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 499e9b76757..471e6e70601 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -32,8 +32,10 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { SearchService } from "@bitwarden/common/vault/abstractions/search.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; +import { skeletonLoadingDelay } from "@bitwarden/common/vault/utils/skeleton-loading.operator"; import { ButtonModule, DialogService, @@ -54,6 +56,7 @@ import { VaultPopupListFiltersService } from "../../services/vault-popup-list-fi import { VaultPopupLoadingService } from "../../services/vault-popup-loading.service"; import { VaultPopupScrollPositionService } from "../../services/vault-popup-scroll-position.service"; import { AtRiskPasswordCalloutComponent } from "../at-risk-callout/at-risk-password-callout.component"; +import { VaultFadeInOutComponent } from "../vault-fade-in-out/vault-fade-in-out.component"; import { VaultFadeInOutSkeletonComponent } from "../vault-fade-in-out-skeleton/vault-fade-in-out-skeleton.component"; import { VaultLoadingSkeletonComponent } from "../vault-loading-skeleton/vault-loading-skeleton.component"; @@ -100,6 +103,7 @@ type VaultState = UnionOfValues<typeof VaultState>; TypographyModule, VaultLoadingSkeletonComponent, VaultFadeInOutSkeletonComponent, + VaultFadeInOutComponent, ], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @@ -129,7 +133,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { }), ); - private skeletonFeatureFlag$ = this.configService.getFeatureFlag$( + protected skeletonFeatureFlag$ = this.configService.getFeatureFlag$( FeatureFlag.VaultLoadingSkeletons, ); @@ -183,9 +187,18 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { map(([loading, skeletonsEnabled]) => loading && !skeletonsEnabled), ); - /** When true, show skeleton loading state */ - protected showSkeletonsLoaders$ = combineLatest([this.loading$, this.skeletonFeatureFlag$]).pipe( - map(([loading, skeletonsEnabled]) => loading && skeletonsEnabled), + /** When true, show skeleton loading state with debouncing to prevent flicker */ + protected showSkeletonsLoaders$ = combineLatest([ + this.loading$, + this.searchService.isCipherSearching$, + this.skeletonFeatureFlag$, + ]).pipe( + map( + ([loading, cipherSearching, skeletonsEnabled]) => + (loading || cipherSearching) && skeletonsEnabled, + ), + distinctUntilChanged(), + skeletonLoadingDelay(), ); protected newItemItemValues$: Observable<NewItemInitialValues> = @@ -228,6 +241,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private liveAnnouncer: LiveAnnouncer, private i18nService: I18nService, private configService: ConfigService, + private searchService: SearchService, ) { combineLatest([ this.vaultPopupItemsService.emptyVault$, diff --git a/libs/common/src/vault/abstractions/search.service.ts b/libs/common/src/vault/abstractions/search.service.ts index 233dee9ec75..29575ec3af9 100644 --- a/libs/common/src/vault/abstractions/search.service.ts +++ b/libs/common/src/vault/abstractions/search.service.ts @@ -6,6 +6,9 @@ import { CipherView } from "../models/view/cipher.view"; import { CipherViewLike } from "../utils/cipher-view-like-utils"; export abstract class SearchService { + abstract isCipherSearching$: Observable<boolean>; + abstract isSendSearching$: Observable<boolean>; + abstract indexedEntityId$(userId: UserId): Observable<IndexedEntityId | null>; abstract clearIndex(userId: UserId): Promise<void>; diff --git a/libs/common/src/vault/services/search.service.ts b/libs/common/src/vault/services/search.service.ts index 80fddda42d5..0b34bd3863f 100644 --- a/libs/common/src/vault/services/search.service.ts +++ b/libs/common/src/vault/services/search.service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as lunr from "lunr"; -import { Observable, firstValueFrom, map } from "rxjs"; +import { BehaviorSubject, Observable, firstValueFrom, map } from "rxjs"; import { Jsonify } from "type-fest"; import { perUserCache$ } from "@bitwarden/common/vault/utils/observable-utilities"; @@ -81,6 +81,12 @@ export class SearchService implements SearchServiceAbstraction { private readonly defaultSearchableMinLength: number = 2; private searchableMinLength: number = this.defaultSearchableMinLength; + private _isCipherSearching$ = new BehaviorSubject<boolean>(false); + isCipherSearching$: Observable<boolean> = this._isCipherSearching$.asObservable(); + + private _isSendSearching$ = new BehaviorSubject<boolean>(false); + isSendSearching$: Observable<boolean> = this._isSendSearching$.asObservable(); + constructor( private logService: LogService, private i18nService: I18nService, @@ -223,6 +229,7 @@ export class SearchService implements SearchServiceAbstraction { filter: ((cipher: C) => boolean) | ((cipher: C) => boolean)[] = null, ciphers: C[], ): Promise<C[]> { + this._isCipherSearching$.next(true); const results: C[] = []; const searchStartTime = performance.now(); if (query != null) { @@ -243,6 +250,7 @@ export class SearchService implements SearchServiceAbstraction { } if (!(await this.isSearchable(userId, query))) { + this._isCipherSearching$.next(false); return ciphers; } @@ -258,6 +266,7 @@ export class SearchService implements SearchServiceAbstraction { // Fall back to basic search if index is not available const basicResults = this.searchCiphersBasic(ciphers, query); this.logService.measure(searchStartTime, "Vault", "SearchService", "basic search complete"); + this._isCipherSearching$.next(false); return basicResults; } @@ -293,6 +302,7 @@ export class SearchService implements SearchServiceAbstraction { }); } this.logService.measure(searchStartTime, "Vault", "SearchService", "search complete"); + this._isCipherSearching$.next(false); return results; } @@ -335,8 +345,10 @@ export class SearchService implements SearchServiceAbstraction { } searchSends(sends: SendView[], query: string) { + this._isSendSearching$.next(true); query = SearchService.normalizeSearchQuery(query.trim().toLocaleLowerCase()); if (query === null) { + this._isSendSearching$.next(false); return sends; } const sendsMatched: SendView[] = []; @@ -359,6 +371,7 @@ export class SearchService implements SearchServiceAbstraction { lowPriorityMatched.push(s); } }); + this._isSendSearching$.next(false); return sendsMatched.concat(lowPriorityMatched); } diff --git a/libs/common/src/vault/utils/skeleton-loading.operator.spec.ts b/libs/common/src/vault/utils/skeleton-loading.operator.spec.ts new file mode 100644 index 00000000000..3ba790f64cb --- /dev/null +++ b/libs/common/src/vault/utils/skeleton-loading.operator.spec.ts @@ -0,0 +1,109 @@ +import { BehaviorSubject } from "rxjs"; + +import { skeletonLoadingDelay } from "./skeleton-loading.operator"; + +describe("skeletonLoadingDelay", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + it("returns false immediately when starting with false", () => { + const source$ = new BehaviorSubject<boolean>(false); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value)); + + expect(results).toEqual([false]); + }); + + it("waits 1 second before returning true when starting with true", () => { + const source$ = new BehaviorSubject<boolean>(true); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value)); + + expect(results).toEqual([]); + + jest.advanceTimersByTime(999); + expect(results).toEqual([]); + + jest.advanceTimersByTime(1); + expect(results).toEqual([true]); + }); + + it("cancels if source becomes false before show delay completes", () => { + const source$ = new BehaviorSubject<boolean>(true); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value)); + + jest.advanceTimersByTime(500); + source$.next(false); + + expect(results).toEqual([false]); + + jest.advanceTimersByTime(1000); + expect(results).toEqual([false]); + }); + + it("delays hiding if minimum display time has not elapsed", () => { + const source$ = new BehaviorSubject<boolean>(true); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value)); + + jest.advanceTimersByTime(1000); + expect(results).toEqual([true]); + + source$.next(false); + + expect(results).toEqual([true]); + + jest.advanceTimersByTime(1000); + expect(results).toEqual([true, false]); + }); + + it("handles rapid true->false->true transitions", () => { + const source$ = new BehaviorSubject<boolean>(true); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay()).subscribe((value) => results.push(value)); + + jest.advanceTimersByTime(500); + expect(results).toEqual([]); + + source$.next(false); + expect(results).toEqual([false]); + + source$.next(true); + + jest.advanceTimersByTime(999); + expect(results).toEqual([false]); + + jest.advanceTimersByTime(1); + expect(results).toEqual([false, true]); + }); + + it("allows for custom timings", () => { + const source$ = new BehaviorSubject<boolean>(true); + const results: boolean[] = []; + + source$.pipe(skeletonLoadingDelay(1000, 2000)).subscribe((value) => results.push(value)); + + jest.advanceTimersByTime(1000); + expect(results).toEqual([true]); + + source$.next(false); + + jest.advanceTimersByTime(1999); + expect(results).toEqual([true]); + + jest.advanceTimersByTime(1); + expect(results).toEqual([true, false]); + }); +}); diff --git a/libs/common/src/vault/utils/skeleton-loading.operator.ts b/libs/common/src/vault/utils/skeleton-loading.operator.ts new file mode 100644 index 00000000000..b9ff28f64b5 --- /dev/null +++ b/libs/common/src/vault/utils/skeleton-loading.operator.ts @@ -0,0 +1,59 @@ +import { defer, Observable, of, timer } from "rxjs"; +import { map, switchMap, tap } from "rxjs/operators"; + +/** + * RxJS operator that adds skeleton loading delay behavior. + * + * - Waits 1 second before showing (prevents flashing for quick loads) + * - Ensures skeleton stays visible for at least 1 second once shown regardless of the source observable emissions + * - After the minimum display time, if the source is still true, continues to emit true until the source becomes false + * - False can only be emitted either: + * - Immediately when the source emits false before the skeleton is shown + * - After the minimum display time has passed once the skeleton is shown + */ +export function skeletonLoadingDelay( + showDelay = 1000, + minDisplayTime = 1000, +): (source: Observable<boolean>) => Observable<boolean> { + return (source: Observable<boolean>) => { + return defer(() => { + let skeletonShownAt: number | null = null; + + return source.pipe( + switchMap((shouldShow): Observable<boolean> => { + if (shouldShow) { + if (skeletonShownAt !== null) { + return of(true); // Already shown, continue showing + } + + // Wait for delay, then mark the skeleton as shown and emit true + return timer(showDelay).pipe( + tap(() => { + skeletonShownAt = Date.now(); + }), + map(() => true), + ); + } else { + if (skeletonShownAt === null) { + // Skeleton not shown yet, can emit false immediately + return of(false); + } + + // Skeleton shown, ensure minimum display time has passed + const elapsedTime = Date.now() - skeletonShownAt; + const remainingTime = Math.max(0, minDisplayTime - elapsedTime); + + // Wait for remaining time to ensure minimum display time + return timer(remainingTime).pipe( + tap(() => { + // Reset the shown timestamp + skeletonShownAt = null; + }), + map(() => false), + ); + } + }), + ); + }); + }; +} From d7949ab2f3763be8599c848289fcb00c67f792ba Mon Sep 17 00:00:00 2001 From: Kyle Spearrin <kspearrin@users.noreply.github.com> Date: Thu, 20 Nov 2025 09:42:57 -0500 Subject: [PATCH 191/249] [PM-27766] Add policy for blocking account creation from claimed domains (#17211) * Added policy for blocking account creation for claimed domains. * add feature flag * fix desc * learn more link * fix localization key to learnMore * onpush change detection --- apps/web/src/locales/en/messages.json | 9 ++++++ ...med-domain-account-creation.component.html | 15 +++++++++ ...aimed-domain-account-creation.component.ts | 32 +++++++++++++++++++ .../policies/policy-edit-definitions/index.ts | 1 + .../policies/policy-edit-register.ts | 2 ++ .../admin-console/enums/policy-type.enum.ts | 1 + libs/common/src/enums/feature-flag.enum.ts | 2 ++ 7 files changed, 62 insertions(+) create mode 100644 bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.html create mode 100644 bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 1b0460e2aa6..59db19aa388 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12122,6 +12122,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.html b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.html new file mode 100644 index 00000000000..17225905995 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.html @@ -0,0 +1,15 @@ +<bit-callout type="info" title="{{ 'prerequisite' | i18n }}"> + {{ "blockClaimedDomainAccountCreationPrerequisite" | i18n }} + <a + bitLink + href="https://bitwarden.com/help/domain-verification/" + target="_blank" + rel="noreferrer" + >{{ "learnMore" | i18n }}</a + > +</bit-callout> + +<bit-form-control> + <input type="checkbox" id="enabled" bitCheckbox [formControl]="enabled" /> + <bit-label>{{ "turnOn" | i18n }}</bit-label> +</bit-form-control> diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts new file mode 100644 index 00000000000..5e2925aa0bb --- /dev/null +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/block-claimed-domain-account-creation.component.ts @@ -0,0 +1,32 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; +import { map, Observable } from "rxjs"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { + BasePolicyEditDefinition, + BasePolicyEditComponent, +} from "@bitwarden/web-vault/app/admin-console/organizations/policies"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +export class BlockClaimedDomainAccountCreationPolicy extends BasePolicyEditDefinition { + name = "blockClaimedDomainAccountCreation"; + description = "blockClaimedDomainAccountCreationDesc"; + type = PolicyType.BlockClaimedDomainAccountCreation; + component = BlockClaimedDomainAccountCreationPolicyComponent; + + override display$(organization: Organization, configService: ConfigService): Observable<boolean> { + return configService + .getFeatureFlag$(FeatureFlag.BlockClaimedDomainAccountCreation) + .pipe(map((enabled) => enabled && organization.useOrganizationDomains)); + } +} + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "block-claimed-domain-account-creation.component.html", + imports: [SharedModule], +}) +export class BlockClaimedDomainAccountCreationPolicyComponent extends BasePolicyEditComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/index.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/index.ts index 52325eae160..b03f3680422 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/index.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-definitions/index.ts @@ -1,3 +1,4 @@ export { ActivateAutofillPolicy } from "./activate-autofill.component"; export { AutomaticAppLoginPolicy } from "./automatic-app-login.component"; +export { BlockClaimedDomainAccountCreationPolicy } from "./block-claimed-domain-account-creation.component"; export { DisablePersonalVaultExportPolicy } from "./disable-personal-vault-export.component"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-register.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-register.ts index 015b4fc17be..c2a31d936b8 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-register.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/policy-edit-register.ts @@ -9,6 +9,7 @@ import { SessionTimeoutPolicy } from "../../key-management/policies/session-time import { ActivateAutofillPolicy, AutomaticAppLoginPolicy, + BlockClaimedDomainAccountCreationPolicy, DisablePersonalVaultExportPolicy, } from "./policy-edit-definitions"; @@ -23,6 +24,7 @@ const policyEditRegister: BasePolicyEditDefinition[] = [ new FreeFamiliesSponsorshipPolicy(), new ActivateAutofillPolicy(), new AutomaticAppLoginPolicy(), + new BlockClaimedDomainAccountCreationPolicy(), ]; export const bitPolicyEditRegister = ossPolicyEditRegister.concat(policyEditRegister); diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index ae0070dda89..af8147c41e4 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -20,4 +20,5 @@ export enum PolicyType { UriMatchDefaults = 16, // Sets the default URI matching strategy for all users within an organization AutotypeDefaultSetting = 17, // Sets the default autotype setting for desktop app AutoConfirm = 18, // Enables the auto confirmation feature for admins to enable in their client + BlockClaimedDomainAccountCreation = 19, // Prevents users from creating personal accounts using email addresses from verified domains } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 7d2d831bfb3..d06a14d242f 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,6 +13,7 @@ export enum FeatureFlag { /* Admin Console Team */ CreateDefaultLocation = "pm-19467-create-default-location", AutoConfirm = "pm-19934-auto-confirm-organization-users", + BlockClaimedDomainAccountCreation = "block-claimed-domain-account-creation", /* Auth */ PM22110_DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods", @@ -91,6 +92,7 @@ export const DefaultFeatureFlagValue = { /* Admin Console Team */ [FeatureFlag.CreateDefaultLocation]: FALSE, [FeatureFlag.AutoConfirm]: FALSE, + [FeatureFlag.BlockClaimedDomainAccountCreation]: FALSE, /* Autofill */ [FeatureFlag.MacOsNativeCredentialSync]: FALSE, From a5caa194cdf010b509fb9dfa0003f5b46fbf6e1d Mon Sep 17 00:00:00 2001 From: Brandon Treston <btreston@bitwarden.com> Date: Thu, 20 Nov 2025 09:51:40 -0500 Subject: [PATCH 192/249] fix copy (#17504) --- .../vnext-organization-data-ownership.component.html | 2 +- apps/web/src/locales/en/messages.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.html b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.html index 0abc40da683..bd2237bc2fd 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.html +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit-definitions/vnext-organization-data-ownership.component.html @@ -1,5 +1,5 @@ <p> - {{ "organizationDataOwnershipContent" | i18n }} + {{ "organizationDataOwnershipDescContent" | i18n }} <a bitLink href="https://bitwarden.com/resources/credential-lifecycle-management/" diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 59db19aa388..0c87e00b26c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5813,9 +5813,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", From e23b2d0c98a024d3ccc1aa25d7bbfb4163662bce Mon Sep 17 00:00:00 2001 From: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:31:05 +0100 Subject: [PATCH 193/249] Autofill/pm 25597 plex password generation (#16997) * Correctly fill generated passwords and current password on plex.tv * Correctly fill generated passwords and current password on plex.tv * Leave existing forEach * Add tests for changes --- .../background/overlay.background.spec.ts | 6 + .../autofill/background/overlay.background.ts | 2 + .../services/abstractions/autofill.service.ts | 3 + .../autofill-overlay-content.service.ts | 6 + .../services/autofill.service.spec.ts | 209 +++++++++++++++++- .../src/autofill/services/autofill.service.ts | 51 ++++- 6 files changed, 259 insertions(+), 18 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index 80e453e9e83..50fb291b121 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -3286,6 +3286,9 @@ describe("OverlayBackground", () => { pageDetails: [pageDetailsForTab], fillNewPassword: true, allowTotpAutofill: true, + focusedFieldForm: undefined, + focusedFieldOpid: undefined, + inlineMenuFillType: undefined, }); expect(overlayBackground["inlineMenuCiphers"].entries()).toStrictEqual( new Map([ @@ -3680,6 +3683,9 @@ describe("OverlayBackground", () => { pageDetails: [overlayBackground["pageDetailsForTab"][sender.tab.id].get(sender.frameId)], fillNewPassword: true, allowTotpAutofill: false, + focusedFieldForm: undefined, + focusedFieldOpid: undefined, + inlineMenuFillType: InlineMenuFillTypes.PasswordGeneration, }); }); }); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index f3278fa6b07..225cbbe66ca 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -1177,6 +1177,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { allowTotpAutofill: true, focusedFieldForm: this.focusedFieldData?.focusedFieldForm, focusedFieldOpid: this.focusedFieldData?.focusedFieldOpid, + inlineMenuFillType: this.focusedFieldData?.inlineMenuFillType, }); if (totpCode) { @@ -1863,6 +1864,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { allowTotpAutofill: false, focusedFieldForm: this.focusedFieldData?.focusedFieldForm, focusedFieldOpid: this.focusedFieldData?.focusedFieldOpid, + inlineMenuFillType: InlineMenuFillTypes.PasswordGeneration, }); globalThis.setTimeout(async () => { diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 85bf8c16610..05bfbf378a8 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -6,6 +6,7 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AutofillMessageCommand } from "../../enums/autofill-message.enums"; +import { InlineMenuFillType } from "../../enums/autofill-overlay.enum"; import AutofillField from "../../models/autofill-field"; import AutofillForm from "../../models/autofill-form"; import AutofillPageDetails from "../../models/autofill-page-details"; @@ -30,6 +31,7 @@ export interface AutoFillOptions { autoSubmitLogin?: boolean; focusedFieldForm?: string; focusedFieldOpid?: string; + inlineMenuFillType?: InlineMenuFillType; } export interface FormData { @@ -49,6 +51,7 @@ export interface GenerateFillScriptOptions { tabUrl: string; defaultUriMatch: UriMatchStrategySetting; focusedFieldOpid?: string; + inlineMenuFillType?: InlineMenuFillType; } export type CollectPageDetailsResponseMessage = { diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 7854dc8e161..817a7cca43c 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1118,6 +1118,12 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ * @param autofillFieldData - Autofill field data captured from the form field element. */ private async setQualifiedLoginFillType(autofillFieldData: AutofillField) { + // Check if this is a current password field in a password change form + if (this.inlineMenuFieldQualificationService.isUpdateCurrentPasswordField(autofillFieldData)) { + autofillFieldData.inlineMenuFillType = InlineMenuFillTypes.CurrentPasswordUpdate; + return; + } + autofillFieldData.inlineMenuFillType = CipherType.Login; autofillFieldData.showPasskeys = autofillFieldData.autoCompleteType.includes("webauthn"); diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index b436214f327..13e97766594 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -44,6 +44,7 @@ import { TotpService } from "@bitwarden/common/vault/services/totp.service"; import { BrowserApi } from "../../platform/browser/browser-api"; import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service"; import { AutofillMessageCommand, AutofillMessageSender } from "../enums/autofill-message.enums"; +import { InlineMenuFillTypes } from "../enums/autofill-overlay.enum"; import { AutofillPort } from "../enums/autofill-port.enum"; import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; @@ -103,6 +104,15 @@ describe("AutofillService", () => { beforeEach(() => { configService = mock<ConfigService>(); configService.getFeatureFlag$.mockImplementation(() => of(false)); + + // Initialize domainSettingsService BEFORE it's used + domainSettingsService = new DefaultDomainSettingsService( + fakeStateProvider, + policyService, + accountService, + ); + domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); + scriptInjectorService = new BrowserScriptInjectorService( domainSettingsService, platformUtilsService, @@ -141,12 +151,6 @@ describe("AutofillService", () => { userNotificationsSettings, messageListener, ); - domainSettingsService = new DefaultDomainSettingsService( - fakeStateProvider, - policyService, - accountService, - ); - domainSettingsService.equivalentDomains$ = of(mockEquivalentDomains); jest.spyOn(BrowserApi, "tabSendMessage"); }); @@ -2077,6 +2081,193 @@ describe("AutofillService", () => { }); }); + describe("given password generation with inlineMenuFillType", () => { + beforeEach(() => { + pageDetails.forms = undefined; + pageDetails.fields = []; // Clear fields to start fresh + options.inlineMenuFillType = InlineMenuFillTypes.PasswordGeneration; + options.cipher.login.totp = null; // Disable TOTP for these tests + }); + + it("includes all password fields from the same form when filling with password generation", async () => { + const newPasswordField = createAutofillFieldMock({ + opid: "new-password", + type: "password", + form: "validFormId", + elementNumber: 2, + }); + const confirmPasswordField = createAutofillFieldMock({ + opid: "confirm-password", + type: "password", + form: "validFormId", + elementNumber: 3, + }); + pageDetails.fields.push(newPasswordField, confirmPasswordField); + options.focusedFieldOpid = newPasswordField.opid; + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(filledFields[newPasswordField.opid]).toBeDefined(); + expect(filledFields[confirmPasswordField.opid]).toBeDefined(); + }); + + it("finds username field for the first password field when generating passwords", async () => { + const newPasswordField = createAutofillFieldMock({ + opid: "new-password", + type: "password", + form: "validFormId", + elementNumber: 2, + }); + pageDetails.fields.push(newPasswordField); + options.focusedFieldOpid = newPasswordField.opid; + jest.spyOn(autofillService as any, "findUsernameField"); + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(autofillService["findUsernameField"]).toHaveBeenCalledWith( + pageDetails, + expect.objectContaining({ opid: newPasswordField.opid }), + false, + false, + true, + ); + }); + + it("does not include password fields from different forms", async () => { + const formAPasswordField = createAutofillFieldMock({ + opid: "form-a-password", + type: "password", + form: "formA", + elementNumber: 1, + }); + const formBPasswordField = createAutofillFieldMock({ + opid: "form-b-password", + type: "password", + form: "formB", + elementNumber: 2, + }); + pageDetails.fields = [formAPasswordField, formBPasswordField]; + options.focusedFieldOpid = formAPasswordField.opid; + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(filledFields[formAPasswordField.opid]).toBeDefined(); + expect(filledFields[formBPasswordField.opid]).toBeUndefined(); + }); + }); + + describe("given current password update with inlineMenuFillType", () => { + beforeEach(() => { + pageDetails.forms = undefined; + pageDetails.fields = []; // Clear fields to start fresh + options.inlineMenuFillType = InlineMenuFillTypes.CurrentPasswordUpdate; + options.cipher.login.totp = null; // Disable TOTP for these tests + }); + + it("includes all password fields from the same form when updating current password", async () => { + const currentPasswordField = createAutofillFieldMock({ + opid: "current-password", + type: "password", + form: "validFormId", + elementNumber: 1, + }); + const newPasswordField = createAutofillFieldMock({ + opid: "new-password", + type: "password", + form: "validFormId", + elementNumber: 2, + }); + const confirmPasswordField = createAutofillFieldMock({ + opid: "confirm-password", + type: "password", + form: "validFormId", + elementNumber: 3, + }); + pageDetails.fields.push(currentPasswordField, newPasswordField, confirmPasswordField); + options.focusedFieldOpid = currentPasswordField.opid; + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(filledFields[currentPasswordField.opid]).toBeDefined(); + expect(filledFields[newPasswordField.opid]).toBeDefined(); + expect(filledFields[confirmPasswordField.opid]).toBeDefined(); + }); + + it("includes all password fields from the same form without TOTP", async () => { + const currentPasswordField = createAutofillFieldMock({ + opid: "current-password", + type: "password", + form: "validFormId", + elementNumber: 1, + }); + const newPasswordField = createAutofillFieldMock({ + opid: "new-password", + type: "password", + form: "validFormId", + elementNumber: 2, + }); + pageDetails.fields.push(currentPasswordField, newPasswordField); + options.focusedFieldOpid = currentPasswordField.opid; + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(filledFields[currentPasswordField.opid]).toBeDefined(); + expect(filledFields[newPasswordField.opid]).toBeDefined(); + }); + + it("does not include password fields from different forms during password update", async () => { + const formAPasswordField = createAutofillFieldMock({ + opid: "form-a-password", + type: "password", + form: "formA", + elementNumber: 1, + }); + const formBPasswordField = createAutofillFieldMock({ + opid: "form-b-password", + type: "password", + form: "formB", + elementNumber: 2, + }); + pageDetails.fields = [formAPasswordField, formBPasswordField]; + options.focusedFieldOpid = formAPasswordField.opid; + + await autofillService["generateLoginFillScript"]( + fillScript, + pageDetails, + filledFields, + options, + ); + + expect(filledFields[formAPasswordField.opid]).toBeDefined(); + expect(filledFields[formBPasswordField.opid]).toBeUndefined(); + }); + }); + describe("given a set of page details that does not contain a password field", () => { let emailField: AutofillField; let emailFieldView: FieldView; @@ -3140,12 +3331,16 @@ describe("AutofillService", () => { "example.com", "exampleapp.com", ]); - domainSettingsService.equivalentDomains$ = of([["not-example.com"]]); const pageUrl = "https://subdomain.example.com"; const tabUrl = "https://www.not-example.com"; const generateFillScriptOptions = createGenerateFillScriptOptionsMock({ tabUrl }); generateFillScriptOptions.cipher.login.matchesUri = jest.fn().mockReturnValueOnce(false); + // Mock getUrlEquivalentDomains to return the expected domains + jest + .spyOn(domainSettingsService, "getUrlEquivalentDomains") + .mockReturnValue(of(equivalentDomains)); + const result = await autofillService["inUntrustedIframe"](pageUrl, generateFillScriptOptions); expect(generateFillScriptOptions.cipher.login.matchesUri).toHaveBeenCalledWith( diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index fcc8861228b..010f5ea0f27 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -52,6 +52,7 @@ import { ScriptInjectorService } from "../../platform/services/abstractions/scri // eslint-disable-next-line no-restricted-imports import { openVaultItemPasswordRepromptPopout } from "../../vault/popup/utils/vault-popout-window"; import { AutofillMessageCommand, AutofillMessageSender } from "../enums/autofill-message.enums"; +import { InlineMenuFillTypes } from "../enums/autofill-overlay.enum"; import { AutofillPort } from "../enums/autofill-port.enum"; import AutofillField from "../models/autofill-field"; import AutofillPageDetails from "../models/autofill-page-details"; @@ -452,6 +453,7 @@ export default class AutofillService implements AutofillServiceInterface { tabUrl: tab.url, defaultUriMatch: defaultUriMatch, focusedFieldOpid: options.focusedFieldOpid, + inlineMenuFillType: options.inlineMenuFillType, }); if (!fillScript || !fillScript.script || !fillScript.script.length) { @@ -971,26 +973,53 @@ export default class AutofillService implements AutofillServiceInterface { if (passwordFields.length && !passwords.length) { // in the event that password fields exist but weren't processed within form elements. - // select matching password if focused, otherwise first in prioritized list. for username, use focused field if it matches, otherwise find field before password. - const passwordFieldToUse = focusedField - ? prioritizedPasswordFields.find(passwordMatchesFocused) || prioritizedPasswordFields[0] - : prioritizedPasswordFields[0]; + const isPasswordGeneration = + options.inlineMenuFillType === InlineMenuFillTypes.PasswordGeneration; + const isCurrentPasswordUpdate = + options.inlineMenuFillType === InlineMenuFillTypes.CurrentPasswordUpdate; - if (passwordFieldToUse) { - passwords.push(passwordFieldToUse); + // For password generation or current password update, include all password fields from the same form + // This ensures we have access to all fields regardless of their login/registration classification + if ((isPasswordGeneration || isCurrentPasswordUpdate) && focusedField) { + // Add all password fields from the same form as the focused field + const focusedFieldForm = focusedField.form; - if (login.username && passwordFieldToUse.elementNumber > 0) { - username = getUsernameForPassword(passwordFieldToUse, true); + // Check both login and registration fields to ensure we get all password fields + const allPasswordFields = [...loginPasswordFields, ...registrationPasswordFields]; + allPasswordFields.forEach((passField) => { + if (passField.form === focusedFieldForm) { + passwords.push(passField); + } + }); + } + + // If we didn't add any passwords above (either not password generation/update or no matching fields), + // select matching password if focused, otherwise first in prioritized list. + if (!passwords.length) { + const passwordFieldToUse = focusedField + ? prioritizedPasswordFields.find(passwordMatchesFocused) || prioritizedPasswordFields[0] + : prioritizedPasswordFields[0]; + + if (passwordFieldToUse) { + passwords.push(passwordFieldToUse); + } + } + + // Handle username and TOTP for the first password field + const firstPasswordField = passwords[0]; + if (firstPasswordField) { + if (login.username && firstPasswordField.elementNumber > 0) { + username = getUsernameForPassword(firstPasswordField, true); if (username) { usernames.set(username.opid, username); } } - if (options.allowTotpAutofill && login.totp && passwordFieldToUse.elementNumber > 0) { + if (options.allowTotpAutofill && login.totp && firstPasswordField.elementNumber > 0) { totp = - isFocusedTotpField && passwordMatchesFocused(passwordFieldToUse) + isFocusedTotpField && passwordMatchesFocused(firstPasswordField) ? focusedField - : this.findTotpField(pageDetails, passwordFieldToUse, false, false, true); + : this.findTotpField(pageDetails, firstPasswordField, false, false, true); if (totp) { totps.push(totp); } From 81453ede1bbdcf13bc0033a886995c9c3993b530 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:45:21 -0500 Subject: [PATCH 194/249] [deps] Vault: Update koa to v2.16.2 [SECURITY] (#15807) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Matt Andreko <mandreko@bitwarden.com> --- apps/cli/package.json | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 00686959ef0..fc38440b70f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -75,7 +75,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.1", + "koa": "2.16.2", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", diff --git a/package-lock.json b/package-lock.json index b017272cd77..dbb3fdb7e2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.1", + "koa": "2.16.2", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.0", @@ -213,7 +213,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.1", + "koa": "2.16.2", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -27947,9 +27947,9 @@ } }, "node_modules/koa": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", - "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.2.tgz", + "integrity": "sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==", "license": "MIT", "dependencies": { "accepts": "^1.3.5", diff --git a/package.json b/package.json index 54e2685bbec..5d23d6b9938 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.1", + "koa": "2.16.2", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.0", From 9afba33f58dd63206d5ebf24f95db1d1624cca17 Mon Sep 17 00:00:00 2001 From: Stephon Brown <sbrown@livefront.com> Date: Thu, 20 Nov 2025 13:38:33 -0500 Subject: [PATCH 195/249] [PM-26044] Update Offboarding Survey for User and Organization (#17472) * feat(billing): update messages to add reasons * feat(billing): update survey with switching reason based on param * fix(billing): revert value of switching reasons * fix(billing): revert removal of tooExpensive message * fix(billing): Add plan type to params and update switching logic * fix(billing): update to include logic * fix(billing): PR feedback --- ...ganization-subscription-cloud.component.ts | 1 + .../shared/offboarding-survey.component.html | 3 +- .../shared/offboarding-survey.component.ts | 90 +++++++++++-------- apps/web/src/locales/en/messages.json | 8 ++ 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 70e16ad3037..e0c1a12a80f 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -344,6 +344,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy data: { type: "Organization", id: this.organizationId, + plan: this.sub.plan.type, }, }); diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.html b/apps/web/src/app/billing/shared/offboarding-survey.component.html index b69565d95fa..50cf71a03d5 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.html +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.html @@ -21,7 +21,8 @@ </bit-label> <textarea rows="4" bitInput formControlName="feedback"></textarea> <bit-hint>{{ - "charactersCurrentAndMaximum" | i18n: formGroup.value.feedback.length : MaxFeedbackLength + "charactersCurrentAndMaximum" + | i18n: formGroup.value.feedback?.length ?? 0 : MaxFeedbackLength }}</bit-hint> </bit-form-field> </div> diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index fe7d724a079..40e1572a3bb 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -1,9 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Component, Inject } from "@angular/core"; +import { ChangeDetectionStrategy, Component, Inject } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { BillingApiServiceAbstraction as BillingApiService } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; +import { PlanType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { @@ -21,6 +22,7 @@ type UserOffboardingParams = { type OrganizationOffboardingParams = { type: "Organization"; id: string; + plan: PlanType; }; export type OffboardingSurveyDialogParams = UserOffboardingParams | OrganizationOffboardingParams; @@ -46,50 +48,20 @@ export const openOffboardingSurvey = ( dialogConfig, ); -// 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: "app-cancel-subscription-form", templateUrl: "offboarding-survey.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, standalone: false, }) export class OffboardingSurveyComponent { protected ResultType = OffboardingSurveyDialogResultType; protected readonly MaxFeedbackLength = 400; - protected readonly reasons: Reason[] = [ - { - value: null, - text: this.i18nService.t("selectPlaceholder"), - }, - { - value: "missing_features", - text: this.i18nService.t("missingFeatures"), - }, - { - value: "switched_service", - text: this.i18nService.t("movingToAnotherTool"), - }, - { - value: "too_complex", - text: this.i18nService.t("tooDifficultToUse"), - }, - { - value: "unused", - text: this.i18nService.t("notUsingEnough"), - }, - { - value: "too_expensive", - text: this.i18nService.t("tooExpensive"), - }, - { - value: "other", - text: this.i18nService.t("other"), - }, - ]; + protected readonly reasons: Reason[] = []; protected formGroup = this.formBuilder.group({ - reason: [this.reasons[0].value, [Validators.required]], + reason: [null, [Validators.required]], feedback: ["", [Validators.maxLength(this.MaxFeedbackLength)]], }); @@ -101,7 +73,35 @@ export class OffboardingSurveyComponent { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private toastService: ToastService, - ) {} + ) { + this.reasons = [ + { + value: null, + text: this.i18nService.t("selectPlaceholder"), + }, + { + value: "missing_features", + text: this.i18nService.t("missingFeatures"), + }, + { + value: "switched_service", + text: this.i18nService.t("movingToAnotherTool"), + }, + { + value: "too_complex", + text: this.i18nService.t("tooDifficultToUse"), + }, + { + value: "unused", + text: this.i18nService.t("notUsingEnough"), + }, + this.getSwitchingReason(), + { + value: "other", + text: this.i18nService.t("other"), + }, + ]; + } submit = async () => { this.formGroup.markAllAsTouched(); @@ -127,4 +127,24 @@ export class OffboardingSurveyComponent { this.dialogRef.close(this.ResultType.Submitted); }; + + private getSwitchingReason(): Reason { + if (this.dialogParams.type === "User") { + return { + value: "too_expensive", + text: this.i18nService.t("switchToFreePlan"), + }; + } + + const isFamilyPlan = [ + PlanType.FamiliesAnnually, + PlanType.FamiliesAnnually2019, + PlanType.FamiliesAnnually2025, + ].includes(this.dialogParams.plan); + + return { + value: "too_expensive", + text: this.i18nService.t(isFamilyPlan ? "switchToFreeOrg" : "tooExpensive"), + }; + } } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 0c87e00b26c..5cf1bea6fd8 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9824,6 +9824,14 @@ "message": "Too expensive", "description": "An option for the offboarding survey shown when a user cancels their subscription." }, + "switchToFreePlan": { + "message": "Switching to free plan", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, + "switchToFreeOrg": { + "message": "Switching to free organization", + "description": "An option for the offboarding survey shown when a user cancels their subscription." + }, "freeForOneYear": { "message": "Free for 1 year" }, From 43897df9ed783f5a38acdc6c14d45283a48ba968 Mon Sep 17 00:00:00 2001 From: Vijay Oommen <voommen@livefront.com> Date: Thu, 20 Nov 2025 12:52:23 -0600 Subject: [PATCH 196/249] [PM-27287] Items in My Items should show in Inactive 2FA report (#17434) --- .../reports/pages/cipher-report.component.ts | 29 +++++----- ...exposed-passwords-report.component.spec.ts | 1 + ...active-two-factor-report.component.spec.ts | 12 ++-- .../inactive-two-factor-report.component.ts | 10 ++-- .../inactive-two-factor-report.component.ts | 57 ++++++++++--------- 5 files changed, 56 insertions(+), 53 deletions(-) diff --git a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts index 69dd360ad31..d098be56663 100644 --- a/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/cipher-report.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, OnDestroy } from "@angular/core"; import { BehaviorSubject, @@ -36,7 +34,7 @@ import { import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/services/admin-console-cipher-form-config.service"; @Directive() -export class CipherReportComponent implements OnDestroy { +export abstract class CipherReportComponent implements OnDestroy { isAdminConsoleActive = false; loading = false; @@ -44,16 +42,16 @@ export class CipherReportComponent implements OnDestroy { ciphers: CipherView[] = []; allCiphers: CipherView[] = []; dataSource = new TableDataSource<CipherView>(); - organization: Organization; - organizations: Organization[]; + organization: Organization | undefined = undefined; + organizations: Organization[] = []; organizations$: Observable<Organization[]>; filterStatus: any = [0]; showFilterToggle: boolean = false; vaultMsg: string = "vault"; - currentFilterStatus: number | string; + currentFilterStatus: number | string = 0; protected filterOrgStatus$ = new BehaviorSubject<number | string>(0); - private destroyed$: Subject<void> = new Subject(); + protected destroyed$: Subject<void> = new Subject(); private vaultItemDialogRef?: DialogRef<VaultItemDialogResult> | undefined; constructor( @@ -107,7 +105,7 @@ export class CipherReportComponent implements OnDestroy { if (filterId === 0) { cipherCount = this.allCiphers.length; } else if (filterId === 1) { - cipherCount = this.allCiphers.filter((c) => c.organizationId === null).length; + cipherCount = this.allCiphers.filter((c) => !c.organizationId).length; } else { this.organizations.filter((org: Organization) => { if (org.id === filterId) { @@ -121,9 +119,9 @@ export class CipherReportComponent implements OnDestroy { } async filterOrgToggle(status: any) { - let filter = null; + let filter = (c: CipherView) => true; if (typeof status === "number" && status === 1) { - filter = (c: CipherView) => c.organizationId == null; + filter = (c: CipherView) => !c.organizationId; } else if (typeof status === "string") { const orgId = status as OrganizationId; filter = (c: CipherView) => c.organizationId === orgId; @@ -185,7 +183,7 @@ export class CipherReportComponent implements OnDestroy { cipher: CipherView, activeCollectionId?: CollectionId, ) { - const disableForm = cipher ? !cipher.edit && !this.organization.canEditAllCiphers : false; + const disableForm = cipher ? !cipher.edit && !this.organization?.canEditAllCiphers : false; this.vaultItemDialogRef = VaultItemDialogComponent.open(this.dialogService, { mode, @@ -230,10 +228,11 @@ export class CipherReportComponent implements OnDestroy { let updatedCipher = await this.cipherService.get(cipher.id, activeUserId); if (this.isAdminConsoleActive) { - updatedCipher = await this.adminConsoleCipherFormConfigService.getCipher( - cipher.id as CipherId, - this.organization, - ); + updatedCipher = + (await this.adminConsoleCipherFormConfigService.getCipher( + cipher.id as CipherId, + this.organization!, + )) ?? updatedCipher; } // convert cipher to cipher view model diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts index 052e3bc7cfe..560245bdc34 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.spec.ts @@ -90,6 +90,7 @@ describe("ExposedPasswordsReportComponent", () => { }); beforeEach(() => { + jest.clearAllMocks(); fixture = TestBed.createComponent(ExposedPasswordsReportComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts index 80893737ffd..64a851e120e 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.spec.ts @@ -1,3 +1,4 @@ +import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { MockProxy, mock } from "jest-mock-extended"; import { of } from "rxjs"; @@ -29,14 +30,13 @@ describe("InactiveTwoFactorReportComponent", () => { const userId = Utils.newGuid() as UserId; const accountService: FakeAccountService = mockAccountServiceWith(userId); - beforeEach(() => { + beforeEach(async () => { let cipherFormConfigServiceMock: MockProxy<CipherFormConfigService>; organizationService = mock<OrganizationService>(); organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock<SyncService>(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - TestBed.configureTestingModule({ + + await TestBed.configureTestingModule({ declarations: [InactiveTwoFactorReportComponent, I18nPipe], providers: [ { @@ -80,9 +80,7 @@ describe("InactiveTwoFactorReportComponent", () => { useValue: adminConsoleCipherFormConfigServiceMock, }, ], - schemas: [], - // FIXME(PM-18598): Replace unknownElements and unknownProperties with actual imports - errorOnUnknownElements: false, + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); }); diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 2a8ec12ac6a..9d7de688f3e 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -1,6 +1,4 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from "@angular/core"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -19,9 +17,8 @@ import { AdminConsoleCipherFormConfigService } from "../../../vault/org-vault/se import { CipherReportComponent } from "./cipher-report.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({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "app-inactive-two-factor-report", templateUrl: "inactive-two-factor-report.component.html", standalone: false, @@ -42,6 +39,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl syncService: SyncService, cipherFormConfigService: CipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected changeDetectorRef: ChangeDetectorRef, ) { super( cipherService, @@ -86,6 +84,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl this.filterCiphersByOrg(inactive2faCiphers); this.cipherDocs = docs; + this.changeDetectorRef.markForCheck(); } } @@ -157,6 +156,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl } this.services.set(serviceData.domain, serviceData.documentation); } + this.changeDetectorRef.markForCheck(); } /** 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 fde9c35a6de..17555e617cb 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 @@ -1,16 +1,12 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit } from "@angular/core"; +import { ChangeDetectorRef, Component, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom, map, takeUntil } from "rxjs"; -import { - getOrganizationById, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { getById } from "@bitwarden/common/platform/misc"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -23,9 +19,8 @@ import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vau import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service"; import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponent } from "../inactive-two-factor-report.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({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "app-inactive-two-factor-report", templateUrl: "../inactive-two-factor-report.component.html", providers: [ @@ -44,7 +39,7 @@ export class InactiveTwoFactorReportComponent implements OnInit { // Contains a list of ciphers, the user running the report, can manage - private manageableCiphers: Cipher[]; + private manageableCiphers: Cipher[] = []; constructor( cipherService: CipherService, @@ -58,6 +53,7 @@ export class InactiveTwoFactorReportComponent syncService: SyncService, cipherFormConfigService: CipherFormConfigService, adminConsoleCipherFormConfigService: AdminConsoleCipherFormConfigService, + protected changeDetectorRef: ChangeDetectorRef, ) { super( cipherService, @@ -70,28 +66,37 @@ export class InactiveTwoFactorReportComponent syncService, cipherFormConfigService, adminConsoleCipherFormConfigService, + changeDetectorRef, ); } async ngOnInit() { this.isAdminConsoleActive = true; - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - this.organization = await firstValueFrom( - this.organizationService - .organizations$(userId) - .pipe(getOrganizationById(params.organizationId)), - ); - this.manageableCiphers = await this.cipherService.getAll(userId); - await super.ngOnInit(); - }); + + this.route.parent?.parent?.params + ?.pipe(takeUntil(this.destroyed$)) + // eslint-disable-next-line rxjs/no-async-subscribe + .subscribe(async (params) => { + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + if (userId) { + this.organization = await firstValueFrom( + this.organizationService.organizations$(userId).pipe(getById(params.organizationId)), + ); + this.manageableCiphers = await this.cipherService.getAll(userId); + await super.ngOnInit(); + } + this.changeDetectorRef.markForCheck(); + }); } - getAllCiphers(): Promise<CipherView[]> { - return this.cipherService.getAllFromApiForOrganization(this.organization.id); + async getAllCiphers(): Promise<CipherView[]> { + if (this.organization) { + return await this.cipherService.getAllFromApiForOrganization(this.organization.id, true); + } + return []; } protected canManageCipher(c: CipherView): boolean { From 98401ccda151d60230911e6b8852b78306c3db5f Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:22:48 -0500 Subject: [PATCH 197/249] PM-28506 - TwoFactorSetupYubikey - refactor yubikey form to be rows with 1 field per row to allow remove button to be visible again. (#17519) --- .../two-factor-setup-yubikey.component.html | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html index 172646f5d4d..8baf304969f 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.html @@ -25,23 +25,21 @@ <li>{{ "twoFactorYubikeySaveForm" | i18n }}</li> </ol> <hr /> - <div class="tw-grid tw-grid-cols-12 tw-gap-4" formArrayName="formKeys"> - <div class="tw-col-span-6" *ngFor="let k of keys; let i = index"> - <div [formGroupName]="i"> - <bit-label>{{ "yubikeyX" | i18n: (i + 1).toString() }}</bit-label> - <bit-form-field *ngIf="!keys[i].existingKey"> - <input bitInput type="password" formControlName="key" appInputVerbatim /> - </bit-form-field> - <div class="tw-flex tw-justify-between tw-mb-6" *ngIf="keys[i].existingKey"> - <span class="tw-mr-2 tw-self-center">{{ keys[i].existingKey }}</span> - <button - bitIconButton="bwi-minus-circle" - type="button" - buttonType="danger" - (click)="remove(i)" - label="{{ 'remove' | i18n }}" - ></button> - </div> + <div class="tw-flex tw-flex-col tw-mt-4" formArrayName="formKeys"> + <div *ngFor="let k of keys; let i = index" [formGroupName]="i"> + <bit-label>{{ "yubikeyX" | i18n: (i + 1).toString() }}</bit-label> + <bit-form-field *ngIf="!keys[i].existingKey"> + <input bitInput type="password" formControlName="key" appInputVerbatim /> + </bit-form-field> + <div class="tw-flex tw-justify-between tw-mb-4" *ngIf="keys[i].existingKey"> + <span class="tw-mr-2 tw-self-center">{{ keys[i].existingKey }}</span> + <button + bitIconButton="bwi-minus-circle" + type="button" + buttonType="danger" + (click)="remove(i)" + label="{{ 'remove' | i18n }}" + ></button> </div> </div> </div> From ba93526965b64d92071d8c29ba496dae54c77920 Mon Sep 17 00:00:00 2001 From: Addison Beck <github@addisonbeck.com> Date: Thu, 20 Nov 2025 19:45:49 -0500 Subject: [PATCH 198/249] chore: create eslint rule to catch insecure page script injection (#17437) * chore: create eslint rule to catch insecure page script injection * chore: ignore existing lints * review: tighten rule scope * review: add tests --- .../fido2-page-script-delay-append.mv2.ts | 3 + eslint.config.mjs | 1 + libs/eslint/platform/index.mjs | 9 +- .../platform/no-page-script-url-leakage.mjs | 115 +++++++++++++ .../no-page-script-url-leakage.spec.mjs | 151 ++++++++++++++++++ 5 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 libs/eslint/platform/no-page-script-url-leakage.mjs create mode 100644 libs/eslint/platform/no-page-script-url-leakage.spec.mjs diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts index 775bc76266d..e167f30af0a 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts @@ -8,6 +8,9 @@ } const script = globalContext.document.createElement("script"); + // This script runs in world: MAIN, eliminating the risk associated with this lint error. + // DOM injection is still needed for the iframe timing hack. + // eslint-disable-next-line @bitwarden/platform/no-page-script-url-leakage script.src = chrome.runtime.getURL("content/fido2-page-script.js"); script.async = false; diff --git a/eslint.config.mjs b/eslint.config.mjs index df98b3af424..7f9bb2284f7 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -80,6 +80,7 @@ export default tseslint.config( "@bitwarden/platform/required-using": "error", "@bitwarden/platform/no-enums": "error", + "@bitwarden/platform/no-page-script-url-leakage": "error", "@bitwarden/components/require-theme-colors-in-svg": "error", "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], diff --git a/libs/eslint/platform/index.mjs b/libs/eslint/platform/index.mjs index c7ea3f1dd89..be78df43e3f 100644 --- a/libs/eslint/platform/index.mjs +++ b/libs/eslint/platform/index.mjs @@ -1,4 +1,11 @@ import requiredUsing from "./required-using.mjs"; import noEnums from "./no-enums.mjs"; +import noPageScriptUrlLeakage from "./no-page-script-url-leakage.mjs"; -export default { rules: { "required-using": requiredUsing, "no-enums": noEnums } }; +export default { + rules: { + "required-using": requiredUsing, + "no-enums": noEnums, + "no-page-script-url-leakage": noPageScriptUrlLeakage, + }, +}; diff --git a/libs/eslint/platform/no-page-script-url-leakage.mjs b/libs/eslint/platform/no-page-script-url-leakage.mjs new file mode 100644 index 00000000000..fe13c496a62 --- /dev/null +++ b/libs/eslint/platform/no-page-script-url-leakage.mjs @@ -0,0 +1,115 @@ +/** + * @fileoverview ESLint rule to prevent page script URL leakage vulnerabilities + * @description This rule detects the specific security vulnerability where DOM script elements + * receive extension URLs through chrome.runtime.getURL() or browser.runtime.getURL() calls. + * This pattern exposes predictable extension URLs to web pages, enabling fingerprinting attacks. + */ + +export const errorMessage = + "Script injection with extension URL exposes asset urls. Use secure page script registration instead."; + +/** + * Checks if a node is a call to chrome.runtime.getURL() or browser.runtime.getURL() + * @param {Object} node - The AST node to check + * @returns {boolean} True if the node is an extension URL call + */ +function isExtensionURLCall(node) { + return ( + node && + node.type === "CallExpression" && + node.callee && + node.callee.type === "MemberExpression" && + node.callee.object && + node.callee.object.type === "MemberExpression" && + node.callee.object.object && + ["chrome", "browser"].includes(node.callee.object.object.name) && + node.callee.object.property && + node.callee.object.property.name === "runtime" && + node.callee.property && + node.callee.property.name === "getURL" + ); +} + +/** + * Checks if a node is a call to createElement("script") + * @param {Object} node - The AST node to check + * @returns {boolean} True if the node creates a script element + */ +function isScriptCreation(node) { + return ( + node && + node.type === "CallExpression" && + node.callee && + node.callee.type === "MemberExpression" && + node.callee.property && + node.callee.property.name === "createElement" && + node.arguments && + node.arguments.length === 1 && + node.arguments[0] && + node.arguments[0].type === "Literal" && + node.arguments[0].value === "script" + ); +} + +export default { + meta: { + type: "problem", + docs: { + description: "Prevent page script URL leakage through extension runtime.getURL calls", + category: "Security", + recommended: true, + }, + schema: [], + messages: { + pageScriptUrlLeakage: errorMessage, + }, + }, + + create(context) { + const scriptVariables = new Set(); + + return { + // Track createElement("script") calls to identify script variables + VariableDeclarator(node) { + if (node.init && isScriptCreation(node.init) && node.id && node.id.name) { + scriptVariables.add(node.id.name); + } + }, + + // Track assignments where script elements are created + AssignmentExpression(node) { + // Track script element creation: variable = document.createElement("script") + if ( + node.operator === "=" && + node.left && + node.left.type === "Identifier" && + isScriptCreation(node.right) + ) { + scriptVariables.add(node.left.name); + } + + // Check for script.src = extension URL pattern + if ( + node.operator === "=" && + node.left && + node.left.type === "MemberExpression" && + node.left.property && + node.left.property.name === "src" && + isExtensionURLCall(node.right) + ) { + // Only flag if this is a script element assignment + if ( + node.left.object && + node.left.object.type === "Identifier" && + scriptVariables.has(node.left.object.name) + ) { + context.report({ + node: node.right, + messageId: "pageScriptUrlLeakage", + }); + } + } + }, + }; + }, +}; diff --git a/libs/eslint/platform/no-page-script-url-leakage.spec.mjs b/libs/eslint/platform/no-page-script-url-leakage.spec.mjs new file mode 100644 index 00000000000..77e1c4d1697 --- /dev/null +++ b/libs/eslint/platform/no-page-script-url-leakage.spec.mjs @@ -0,0 +1,151 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import rule, { errorMessage } from "./no-page-script-url-leakage.mjs"; + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + project: [__dirname + "/../tsconfig.spec.json"], + projectService: { + allowDefaultProject: ["*.ts*"], + }, + tsconfigRootDir: __dirname + "/..", + }, + }, +}); + +ruleTester.run("no-page-script-url-leakage", rule.default, { + valid: [ + { + name: "Non-script element with extension URL (iframe)", + code: ` + const iframe = document.createElement("iframe"); + iframe.src = chrome.runtime.getURL("popup.html"); + `, + }, + { + name: "Non-script element with extension URL (img)", + code: ` + const img = document.createElement("img"); + img.src = chrome.runtime.getURL("icon.png"); + `, + }, + { + name: "Script element with non-extension URL", + code: ` + const script = document.createElement("script"); + script.src = "https://example.com/script.js"; + `, + }, + { + name: "Extension URL call without DOM assignment", + code: ` + const url = chrome.runtime.getURL("assets/icon.png"); + console.log(url); + `, + }, + { + name: "Browser runtime call without DOM assignment", + code: ` + const url = browser.runtime.getURL("content/style.css"); + fetch(url); + `, + }, + { + name: "Script assignment with variable not from createElement", + code: ` + const script = getSomeScriptElement(); + script.src = chrome.runtime.getURL("script.js"); + `, + }, + { + name: "Assignment to different property", + code: ` + const script = document.createElement("script"); + script.type = "text/javascript"; + `, + }, + ], + invalid: [ + { + name: "Script element with chrome.runtime.getURL - variable declaration", + code: ` + const script = document.createElement("script"); + script.src = chrome.runtime.getURL("content/script.js"); + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Script element with browser.runtime.getURL - variable declaration", + code: ` + const script = document.createElement("script"); + script.src = browser.runtime.getURL("content/script.js"); + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Script element with chrome.runtime.getURL - assignment expression", + code: ` + let script; + script = document.createElement("script"); + script.src = chrome.runtime.getURL("page-script.js"); + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Script element with browser.runtime.getURL - assignment expression", + code: ` + let element; + element = document.createElement("script"); + element.src = browser.runtime.getURL("fido2-page-script.js"); + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Multiple script elements with different variable names", + code: ` + const scriptA = document.createElement("script"); + const scriptB = document.createElement("script"); + scriptA.src = chrome.runtime.getURL("script-a.js"); + scriptB.src = browser.runtime.getURL("script-b.js"); + `, + errors: [ + { + message: errorMessage, + }, + { + message: errorMessage, + }, + ], + }, + { + name: "Real-world pattern that prompted creation of this lint rule", + code: ` + const script = globalThis.document.createElement("script"); + script.src = chrome.runtime.getURL("content/fido2-page-script.js"); + script.async = false; + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + ], +}); From 8077270ef87d2eb1182d5ec88db80daf2f0eea69 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:39:50 +0100 Subject: [PATCH 199/249] Autosync the updated translations (#17529) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/browser/src/_locales/ar/messages.json | 3 + apps/browser/src/_locales/az/messages.json | 9 ++- apps/browser/src/_locales/be/messages.json | 3 + apps/browser/src/_locales/bg/messages.json | 3 + apps/browser/src/_locales/bn/messages.json | 3 + apps/browser/src/_locales/bs/messages.json | 3 + apps/browser/src/_locales/ca/messages.json | 3 + apps/browser/src/_locales/cs/messages.json | 3 + apps/browser/src/_locales/cy/messages.json | 3 + apps/browser/src/_locales/da/messages.json | 3 + apps/browser/src/_locales/de/messages.json | 7 +- apps/browser/src/_locales/el/messages.json | 3 + apps/browser/src/_locales/en_GB/messages.json | 3 + apps/browser/src/_locales/en_IN/messages.json | 3 + apps/browser/src/_locales/es/messages.json | 3 + apps/browser/src/_locales/et/messages.json | 3 + apps/browser/src/_locales/eu/messages.json | 3 + apps/browser/src/_locales/fa/messages.json | 3 + apps/browser/src/_locales/fi/messages.json | 17 +++-- apps/browser/src/_locales/fil/messages.json | 3 + apps/browser/src/_locales/fr/messages.json | 71 ++++++++++--------- apps/browser/src/_locales/gl/messages.json | 3 + apps/browser/src/_locales/he/messages.json | 3 + apps/browser/src/_locales/hi/messages.json | 3 + apps/browser/src/_locales/hr/messages.json | 3 + apps/browser/src/_locales/hu/messages.json | 3 + apps/browser/src/_locales/id/messages.json | 3 + apps/browser/src/_locales/it/messages.json | 3 + apps/browser/src/_locales/ja/messages.json | 7 +- apps/browser/src/_locales/ka/messages.json | 3 + apps/browser/src/_locales/km/messages.json | 3 + apps/browser/src/_locales/kn/messages.json | 3 + apps/browser/src/_locales/ko/messages.json | 3 + apps/browser/src/_locales/lt/messages.json | 3 + apps/browser/src/_locales/lv/messages.json | 9 ++- apps/browser/src/_locales/ml/messages.json | 3 + apps/browser/src/_locales/mr/messages.json | 3 + apps/browser/src/_locales/my/messages.json | 3 + apps/browser/src/_locales/nb/messages.json | 3 + apps/browser/src/_locales/ne/messages.json | 3 + apps/browser/src/_locales/nl/messages.json | 3 + apps/browser/src/_locales/nn/messages.json | 3 + apps/browser/src/_locales/or/messages.json | 3 + apps/browser/src/_locales/pl/messages.json | 3 + apps/browser/src/_locales/pt_BR/messages.json | 3 + apps/browser/src/_locales/pt_PT/messages.json | 3 + apps/browser/src/_locales/ro/messages.json | 3 + apps/browser/src/_locales/ru/messages.json | 3 + apps/browser/src/_locales/si/messages.json | 3 + apps/browser/src/_locales/sk/messages.json | 3 + apps/browser/src/_locales/sl/messages.json | 3 + apps/browser/src/_locales/sr/messages.json | 3 + apps/browser/src/_locales/sv/messages.json | 3 + apps/browser/src/_locales/ta/messages.json | 3 + apps/browser/src/_locales/te/messages.json | 3 + apps/browser/src/_locales/th/messages.json | 3 + apps/browser/src/_locales/tr/messages.json | 3 + apps/browser/src/_locales/uk/messages.json | 3 + apps/browser/src/_locales/vi/messages.json | 3 + apps/browser/src/_locales/zh_CN/messages.json | 3 + apps/browser/src/_locales/zh_TW/messages.json | 9 ++- 61 files changed, 237 insertions(+), 54 deletions(-) diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json index 20eb31a5453..505a8404233 100644 --- a/apps/browser/src/_locales/ar/messages.json +++ b/apps/browser/src/_locales/ar/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "تعيين كلمة مرور رئيسية" }, diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json index d8d8589f47e..086b66380b5 100644 --- a/apps/browser/src/_locales/az/messages.json +++ b/apps/browser/src/_locales/az/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Ana parolu ayarla" }, @@ -5810,13 +5813,13 @@ "message": "\"Premium\"a yüksəlt" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "Tam təhlükəsizlik üçün yüksəldin" }, "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "message": "Premium, güvəndə qalmağınız, səmərəli çalışmağınız və nəzarətə sahib olmağınız üçün daha çox alət verir." }, "explorePremium": { - "message": "Explore Premium" + "message": "Premium-u kəşf et" }, "loadingVault": { "message": "Seyf yüklənir" diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json index 47bafb7efe3..16a6d739962 100644 --- a/apps/browser/src/_locales/be/messages.json +++ b/apps/browser/src/_locales/be/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Прызначыць асноўны пароль" }, diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json index 98da9bf033e..3dba65d6aa7 100644 --- a/apps/browser/src/_locales/bg/messages.json +++ b/apps/browser/src/_locales/bg/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Задаване на главна парола" }, diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json index 794e380d012..d2519cb13e3 100644 --- a/apps/browser/src/_locales/bn/messages.json +++ b/apps/browser/src/_locales/bn/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "মূল পাসওয়ার্ড ধার্য করুন" }, diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json index 611cb4f5f55..917180579f2 100644 --- a/apps/browser/src/_locales/bs/messages.json +++ b/apps/browser/src/_locales/bs/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json index 01611cc0764..a9ebdf139d7 100644 --- a/apps/browser/src/_locales/ca/messages.json +++ b/apps/browser/src/_locales/ca/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Estableix la contrasenya mestra" }, diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json index 5c0c8fbf524..ca5d4b09f28 100644 --- a/apps/browser/src/_locales/cs/messages.json +++ b/apps/browser/src/_locales/cs/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Tato stránka narušuje zážitek z Bitwardenu. Vložené menu Bitwarden bylo dočasně vypnuto jako bezpečnostní opatření." + }, "setMasterPassword": { "message": "Nastavit hlavní heslo" }, diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json index 3bd81cc8039..1f83ff72f62 100644 --- a/apps/browser/src/_locales/cy/messages.json +++ b/apps/browser/src/_locales/cy/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Gosod prif gyfrinair" }, diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json index 9a12f808f22..96aa81ce876 100644 --- a/apps/browser/src/_locales/da/messages.json +++ b/apps/browser/src/_locales/da/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Indstil hovedadgangskode" }, diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json index 517dc638aff..b32c9a68c06 100644 --- a/apps/browser/src/_locales/de/messages.json +++ b/apps/browser/src/_locales/de/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Master-Passwort festlegen" }, @@ -5813,10 +5816,10 @@ "message": "Upgrade für umfassende Sicherheit" }, "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "message": "Premium gibt dir mehr Werkzeuge, um sicher zu bleiben, effizient zu arbeiten und die Kontrolle zu behalten." }, "explorePremium": { - "message": "Explore Premium" + "message": "Premium erkunden" }, "loadingVault": { "message": "Tresor wird geladen" diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json index ae685fff651..f4c3c0d53a5 100644 --- a/apps/browser/src/_locales/el/messages.json +++ b/apps/browser/src/_locales/el/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Ορισμός κύριου κωδικού πρόσβασης" }, diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json index 5079e1e6689..96c3323faef 100644 --- a/apps/browser/src/_locales/en_GB/messages.json +++ b/apps/browser/src/_locales/en_GB/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json index 2a34400ea58..b9f777148e3 100644 --- a/apps/browser/src/_locales/en_IN/messages.json +++ b/apps/browser/src/_locales/en_IN/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json index 8100fa80435..92dbe15fad2 100644 --- a/apps/browser/src/_locales/es/messages.json +++ b/apps/browser/src/_locales/es/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Establecer contraseña maestra" }, diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json index 0f69561df57..bb029bf7777 100644 --- a/apps/browser/src/_locales/et/messages.json +++ b/apps/browser/src/_locales/et/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Määra ülemparool" }, diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json index c60ce5e4da7..06a4f8ea48d 100644 --- a/apps/browser/src/_locales/eu/messages.json +++ b/apps/browser/src/_locales/eu/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Ezarri pasahitz nagusia" }, diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json index 5a03f8427fa..33f4a02277d 100644 --- a/apps/browser/src/_locales/fa/messages.json +++ b/apps/browser/src/_locales/fa/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "تنظیم کلمه عبور اصلی" }, diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json index db4649c33a7..9953782f504 100644 --- a/apps/browser/src/_locales/fi/messages.json +++ b/apps/browser/src/_locales/fi/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Aseta pääsalasana" }, @@ -4984,7 +4987,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Oletus ( $VALUE$ )", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5656,7 +5659,7 @@ "message": "Close this tab" }, "phishingPageContinueV2": { - "message": "Continue to this site (not recommended)" + "message": "Jatka tälle sivustolle (ei suositeltavaa)" }, "phishingPageExplanation1": { "message": "This site was found in ", @@ -5773,7 +5776,7 @@ "message": "Show less" }, "next": { - "message": "Next" + "message": "Seuraava" }, "moreBreadcrumbs": { "message": "More breadcrumbs", @@ -5819,20 +5822,20 @@ "message": "Explore Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Ladataan holvia" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Holvi ladattu" }, "settingDisabledByPolicy": { "message": "This setting is disabled by your organization's policy.", "description": "This hint text is displayed when a user setting is disabled due to an organization policy." }, "zipPostalCodeLabel": { - "message": "ZIP / Postal code" + "message": "Postinumero" }, "cardNumberLabel": { - "message": "Card number" + "message": "Kortin numero" }, "sessionTimeoutSettingsAction": { "message": "Timeout action" diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json index 6239e9d4f97..687863550a7 100644 --- a/apps/browser/src/_locales/fil/messages.json +++ b/apps/browser/src/_locales/fil/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Itakda ang master password" }, diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json index 1cc04809a1b..87c1a20a38a 100644 --- a/apps/browser/src/_locales/fr/messages.json +++ b/apps/browser/src/_locales/fr/messages.json @@ -32,7 +32,7 @@ "message": "Utiliser l'authentification unique" }, "yourOrganizationRequiresSingleSignOn": { - "message": "Your organization requires single sign-on." + "message": "Votre organisation exige l’authentification unique." }, "welcomeBack": { "message": "Content de vous revoir" @@ -592,10 +592,10 @@ "message": "Afficher" }, "viewAll": { - "message": "View all" + "message": "Tout afficher" }, "viewLess": { - "message": "View less" + "message": "Afficher moins" }, "viewLogin": { "message": "Afficher l'Identifiant" @@ -800,10 +800,10 @@ "message": "Au verrouillage" }, "onIdle": { - "message": "On system idle" + "message": "À l'inactivité du système" }, "onSleep": { - "message": "On system sleep" + "message": "À la mise en veille du système" }, "onRestart": { "message": "Au redémarrage du navigateur" @@ -1044,10 +1044,10 @@ "message": "Élément enregistré" }, "savedWebsite": { - "message": "Saved website" + "message": "Site Web enregistré" }, "savedWebsites": { - "message": "Saved websites ( $COUNT$ )", + "message": "Sites Web enregistrés ( $COUNT$)", "placeholders": { "count": { "content": "$1", @@ -1645,7 +1645,7 @@ "message": "Vous devez ajouter soit l'URL du serveur de base, soit au moins un environnement personnalisé." }, "selfHostedEnvMustUseHttps": { - "message": "URLs must use HTTPS." + "message": "Les URL doivent utiliser HTTPS." }, "customEnvironment": { "message": "Environnement personnalisé" @@ -1701,28 +1701,28 @@ "message": "Désactiver la saisie automatique" }, "confirmAutofill": { - "message": "Confirm autofill" + "message": "Confirmer la saisie automatique" }, "confirmAutofillDesc": { - "message": "This site doesn't match your saved login details. Before you fill in your login credentials, make sure it's a trusted site." + "message": "Ce site ne correspond pas à vos identifiants de connexion enregistrés. Avant de remplir vos identifiants de connexion, assurez-vous que c'est un site de confiance." }, "showInlineMenuLabel": { "message": "Afficher les suggestions de saisie automatique dans les champs d'un formulaire" }, "howDoesBitwardenProtectFromPhishing": { - "message": "How does Bitwarden protect your data from phishing?" + "message": "Comment Bitwarden protège-t-il vos données contre l'hameçonnage ?" }, "currentWebsite": { - "message": "Current website" + "message": "Site internet actuel" }, "autofillAndAddWebsite": { - "message": "Autofill and add this website" + "message": "Saisir automatiquement et ajouter ce site" }, "autofillWithoutAdding": { - "message": "Autofill without adding" + "message": "Saisir automatiquement sans ajouter" }, "doNotAutofill": { - "message": "Do not autofill" + "message": "Ne pas saisir automatiquement" }, "showInlineMenuIdentitiesLabel": { "message": "Afficher les identités sous forme de suggestions" @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Définir le mot de passe principal" }, @@ -3286,7 +3289,7 @@ "message": "Erreur de déchiffrement" }, "errorGettingAutoFillData": { - "message": "Error getting autofill data" + "message": "Erreur lors de l'obtention des données de saisie automatique" }, "couldNotDecryptVaultItemsBelow": { "message": "Bitwarden n’a pas pu déchiffrer le(s) élément(s) du coffre listé(s) ci-dessous." @@ -4060,13 +4063,13 @@ "description": "Toast message for informing the user that autofill on page load has been set to the default setting." }, "cannotAutofill": { - "message": "Cannot autofill" + "message": "Impossible de saisir automatiquement" }, "cannotAutofillExactMatch": { - "message": "Default matching is set to 'Exact Match'. The current website does not exactly match the saved login details for this item." + "message": "La correspondance par défaut est définie à 'Correspondance exacte'. Le site internet actuel ne correspond pas exactement aux informations de l'identifiant de connexion enregistrées pour cet élément." }, "okay": { - "message": "Okay" + "message": "Ok" }, "toggleSideNavigation": { "message": "Basculer la navigation latérale" @@ -4984,7 +4987,7 @@ } }, "defaultLabelWithValue": { - "message": "Default ( $VALUE$ )", + "message": "Par défaut ($VALUE$)", "description": "A label that indicates the default value for a field with the current default value in parentheses.", "placeholders": { "value": { @@ -5786,43 +5789,43 @@ "message": "Excellent travail pour sécuriser vos identifiants à risque !" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Mettre à niveau maintenant" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Authentificateur intégré" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Stockage sécurisé de fichier" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Accès d'urgence" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Surveillance des fuites" }, "andMoreFeatures": { - "message": "And more!" + "message": "Et encore plus !" }, "planDescPremium": { - "message": "Complete online security" + "message": "Sécurité en ligne complète" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Mettre à niveau vers Premium" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "Mettre à niveau pour une sécurité complète" }, "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "message": "Premium vous donne plus d'outils pour rester en sécurité, travailler efficacement et garder le contrôle." }, "explorePremium": { - "message": "Explore Premium" + "message": "Explorer Premium" }, "loadingVault": { - "message": "Loading vault" + "message": "Chargement du coffre" }, "vaultLoaded": { - "message": "Vault loaded" + "message": "Coffre chargé" }, "settingDisabledByPolicy": { "message": "Ce paramètre est désactivé par la politique de sécurité de votre organisation.", @@ -5835,6 +5838,6 @@ "message": "Numéro de carte" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Action à l’expiration" } } diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json index 090bc6b1493..9b35af1aad4 100644 --- a/apps/browser/src/_locales/gl/messages.json +++ b/apps/browser/src/_locales/gl/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Definir contrasinal mestre" }, diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json index 2934d345600..7d1700cbbdc 100644 --- a/apps/browser/src/_locales/he/messages.json +++ b/apps/browser/src/_locales/he/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "הגדר סיסמה ראשית" }, diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json index 75011ebf8e5..0af38bf6964 100644 --- a/apps/browser/src/_locales/hi/messages.json +++ b/apps/browser/src/_locales/hi/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "मास्टर पासवर्ड सेट करें" }, diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json index fb89e8940e3..9bb5ca08843 100644 --- a/apps/browser/src/_locales/hr/messages.json +++ b/apps/browser/src/_locales/hr/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Postavi glavnu lozinku" }, diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json index f9a2f9ff009..9b6a5d756d5 100644 --- a/apps/browser/src/_locales/hu/messages.json +++ b/apps/browser/src/_locales/hu/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Ez az oldal zavarja a Bitwarden élményt. Biztonsági intézkedésként ideiglenesen letiltásra került a Bitwarden belső menü." + }, "setMasterPassword": { "message": "Mesterjelszó beállítása" }, diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json index a88e3cc2d37..85fdfbf9afe 100644 --- a/apps/browser/src/_locales/id/messages.json +++ b/apps/browser/src/_locales/id/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Atur Kata Sandi Utama" }, diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json index f0e63d99ecf..a76bb05d15a 100644 --- a/apps/browser/src/_locales/it/messages.json +++ b/apps/browser/src/_locales/it/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Imposta password principale" }, diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json index 98ac152a08f..1294335481c 100644 --- a/apps/browser/src/_locales/ja/messages.json +++ b/apps/browser/src/_locales/ja/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "マスターパスワードを設定" }, @@ -3365,7 +3368,7 @@ "message": "サービス" }, "forwardedEmail": { - "message": "転送されたメールエイリアス" + "message": "転送されるメールエイリアス" }, "forwardedEmailDesc": { "message": "外部転送サービスを使用してメールエイリアスを生成します。" @@ -4517,7 +4520,7 @@ "description": "Label indicating the most common import formats" }, "uriMatchDefaultStrategyHint": { - "message": "URI match detection is how Bitwarden identifies autofill suggestions.", + "message": "URI の一致検出方法は、Bitwarden が自動入力候補をどのように判別するかを指定します。", "description": "Explains to the user that URI match detection determines how Bitwarden suggests autofill options, and clarifies that this default strategy applies when no specific match detection is set for a login item." }, "regExAdvancedOptionWarning": { diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json index e6e8becd50a..52e9fbc5229 100644 --- a/apps/browser/src/_locales/ka/messages.json +++ b/apps/browser/src/_locales/ka/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/km/messages.json +++ b/apps/browser/src/_locales/km/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json index c83d63762d6..d2ca68a0108 100644 --- a/apps/browser/src/_locales/kn/messages.json +++ b/apps/browser/src/_locales/kn/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "ಮಾಸ್ಟರ್ ಪಾಸ್ವರ್ಡ್ ಹೊಂದಿಸಿ" }, diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json index 50f49833d50..c583e173d91 100644 --- a/apps/browser/src/_locales/ko/messages.json +++ b/apps/browser/src/_locales/ko/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "마스터 비밀번호 설정" }, diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json index 21cd9ca401c..45ee71f75dd 100644 --- a/apps/browser/src/_locales/lt/messages.json +++ b/apps/browser/src/_locales/lt/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Pagrindinio slaptažodžio nustatymas" }, diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json index 088d671a2b5..d7e4b5eea9c 100644 --- a/apps/browser/src/_locales/lv/messages.json +++ b/apps/browser/src/_locales/lv/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Šī lapa traucē Bitwarden darbību. Bitwarden iekļautā izvēlne ir īslaicīgi atspējot kā drošības mērs." + }, "setMasterPassword": { "message": "Uzstādīt galveno paroli" }, @@ -5810,13 +5813,13 @@ "message": "Uzlabot uz Premium" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "Uzlabo pilnīgas drošības iegūšanai" }, "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "message": "Premium sniedz vairāk rīku drošībai, darba ražīgumam un pārraudzībai." }, "explorePremium": { - "message": "Explore Premium" + "message": "Izpētīt Premium" }, "loadingVault": { "message": "Ielādē glabātavu" diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json index d76f579fac4..6c022f0043f 100644 --- a/apps/browser/src/_locales/ml/messages.json +++ b/apps/browser/src/_locales/ml/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "പ്രാഥമിക പാസ്‌വേഡ് സജ്ജമാക്കുക" }, diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json index acbbc97ceac..d34d1c87971 100644 --- a/apps/browser/src/_locales/mr/messages.json +++ b/apps/browser/src/_locales/mr/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/my/messages.json +++ b/apps/browser/src/_locales/my/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json index 3933f66c541..11bd78ed56c 100644 --- a/apps/browser/src/_locales/nb/messages.json +++ b/apps/browser/src/_locales/nb/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Angi hovedpassord" }, diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/ne/messages.json +++ b/apps/browser/src/_locales/ne/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json index a4d38f6c9ba..8817e04b163 100644 --- a/apps/browser/src/_locales/nl/messages.json +++ b/apps/browser/src/_locales/nl/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Deze pagina verstoort de Bitwarden-ervaring. Het inline-menu van Bitwarden is tijdelijk uitgeschakeld als veiligheidsmaatregel." + }, "setMasterPassword": { "message": "Hoofdwachtwoord instellen" }, diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/nn/messages.json +++ b/apps/browser/src/_locales/nn/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/or/messages.json +++ b/apps/browser/src/_locales/or/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json index 0847126f33e..41f679aba50 100644 --- a/apps/browser/src/_locales/pl/messages.json +++ b/apps/browser/src/_locales/pl/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Ustaw hasło główne" }, diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json index dbd7229db2a..9e5d2331744 100644 --- a/apps/browser/src/_locales/pt_BR/messages.json +++ b/apps/browser/src/_locales/pt_BR/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Definir senha principal" }, diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json index 55065838ec8..4fd291e5c89 100644 --- a/apps/browser/src/_locales/pt_PT/messages.json +++ b/apps/browser/src/_locales/pt_PT/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Esta página está a interferir com a experiência do Bitwarden. O menu em linha do Bitwarden foi temporariamente desativado como medida de segurança." + }, "setMasterPassword": { "message": "Definir a palavra-passe mestra" }, diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json index 84a1937bf14..66a5b9d796b 100644 --- a/apps/browser/src/_locales/ro/messages.json +++ b/apps/browser/src/_locales/ro/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Setare parolă principală" }, diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json index 22e9052fe43..1f3d7c7234f 100644 --- a/apps/browser/src/_locales/ru/messages.json +++ b/apps/browser/src/_locales/ru/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Эта страница мешает работе Bitwarden. Встроенное меню Bitwarden было временно отключено в целях безопасности." + }, "setMasterPassword": { "message": "Задать мастер-пароль" }, diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json index f4c00449a2b..61dc029754a 100644 --- a/apps/browser/src/_locales/si/messages.json +++ b/apps/browser/src/_locales/si/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "මාස්ටර් මුරපදය සකසන්න" }, diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json index b8b820def35..865e832fda3 100644 --- a/apps/browser/src/_locales/sk/messages.json +++ b/apps/browser/src/_locales/sk/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Táto stránka narúša zážitok zo Bitwardenu. Inline ponuka Bitwardenu bola dočasne vypnutá ako bezpečnostné opatrenie." + }, "setMasterPassword": { "message": "Nastaviť hlavné heslo" }, diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json index 70d9c5f70c2..ebb245290f9 100644 --- a/apps/browser/src/_locales/sl/messages.json +++ b/apps/browser/src/_locales/sl/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Nastavi glavno geslo" }, diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json index 97fcb37fb58..d54a6ba928f 100644 --- a/apps/browser/src/_locales/sr/messages.json +++ b/apps/browser/src/_locales/sr/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Постави Главну Лозинку" }, diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json index ea7a4d80359..245692a27aa 100644 --- a/apps/browser/src/_locales/sv/messages.json +++ b/apps/browser/src/_locales/sv/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Denna sida stör Bitwarden-upplevelsen. Bitwardens inbyggda meny har tillfälligt inaktiverats som en säkerhetsåtgärd." + }, "setMasterPassword": { "message": "Ange huvudlösenord" }, diff --git a/apps/browser/src/_locales/ta/messages.json b/apps/browser/src/_locales/ta/messages.json index a95da4d2059..a6e2ad0ee31 100644 --- a/apps/browser/src/_locales/ta/messages.json +++ b/apps/browser/src/_locales/ta/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "முதன்மை கடவுச்சொல்லை அமை" }, diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json index cb1a5c089fe..7c4dbaf85dc 100644 --- a/apps/browser/src/_locales/te/messages.json +++ b/apps/browser/src/_locales/te/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Set master password" }, diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json index 9096067ce3b..ff0c05a470a 100644 --- a/apps/browser/src/_locales/th/messages.json +++ b/apps/browser/src/_locales/th/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "ตั้งรหัสผ่านหลัก" }, diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json index 4c7ca6937a4..b2bff83e8a9 100644 --- a/apps/browser/src/_locales/tr/messages.json +++ b/apps/browser/src/_locales/tr/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "Bu sayfa Bitwarden deneyimiyle çakışıyor. Güvenlik önlemi olarak Bitwarden satır içi menüsü geçici olarak devre dışı bırakıldı." + }, "setMasterPassword": { "message": "Ana parolayı belirle" }, diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json index 7f8b0f8b13b..b104f845fa7 100644 --- a/apps/browser/src/_locales/uk/messages.json +++ b/apps/browser/src/_locales/uk/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Встановити головний пароль" }, diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json index 414b4cc2cac..242b779ca26 100644 --- a/apps/browser/src/_locales/vi/messages.json +++ b/apps/browser/src/_locales/vi/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "This page is interfering with the Bitwarden experience. The Bitwarden inline menu has been temporarily disabled as a safety measure." + }, "setMasterPassword": { "message": "Đặt mật khẩu chính" }, diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json index 548bdd9e178..cf1664b6a6f 100644 --- a/apps/browser/src/_locales/zh_CN/messages.json +++ b/apps/browser/src/_locales/zh_CN/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "此页面正在干扰 Bitwarden 的使用体验。出于安全考虑,Bitwarden 内嵌菜单已被暂时禁用。" + }, "setMasterPassword": { "message": "设置主密码" }, diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json index 600447a29f3..0ef20edce81 100644 --- a/apps/browser/src/_locales/zh_TW/messages.json +++ b/apps/browser/src/_locales/zh_TW/messages.json @@ -2436,6 +2436,9 @@ } } }, + "topLayerHijackWarning": { + "message": "此頁面正在干擾 Bitwarden 的使用體驗。為了安全起見,已暫時停用 Bitwarden 的內嵌選單。" + }, "setMasterPassword": { "message": "設定主密碼" }, @@ -5810,13 +5813,13 @@ "message": "升級到 Premium" }, "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "message": "升級以獲得完整的安全防護" }, "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "message": "進階版提供更多工具,協助您維持安全、高效工作並保持掌控。" }, "explorePremium": { - "message": "Explore Premium" + "message": "探索進階版" }, "loadingVault": { "message": "正在載入密碼庫" From 5ff6e50e1d51af9177046aa7f7ce408abbf174ea Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:40:12 +0100 Subject: [PATCH 200/249] Autosync the updated translations (#17530) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/desktop/src/locales/fr/messages.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json index acd5037bb6d..6cca98444b8 100644 --- a/apps/desktop/src/locales/fr/messages.json +++ b/apps/desktop/src/locales/fr/messages.json @@ -70,7 +70,7 @@ } }, "noEditPermissions": { - "message": "You don't have permission to edit this item" + "message": "Vous n'avez pas l'autorisation de modifier cet élément" }, "welcomeBack": { "message": "Content de vous revoir" @@ -4195,33 +4195,33 @@ "message": "Numéro de carte" }, "upgradeNow": { - "message": "Upgrade now" + "message": "Mettre à niveau maintenant" }, "builtInAuthenticator": { - "message": "Built-in authenticator" + "message": "Authentificateur intégré" }, "secureFileStorage": { - "message": "Secure file storage" + "message": "Stockage sécurisé de fichier" }, "emergencyAccess": { - "message": "Emergency access" + "message": "Accès d'urgence" }, "breachMonitoring": { - "message": "Breach monitoring" + "message": "Surveillance des fuites" }, "andMoreFeatures": { - "message": "And more!" + "message": "Et encore plus !" }, "planDescPremium": { - "message": "Complete online security" + "message": "Sécurité en ligne complète" }, "upgradeToPremium": { - "message": "Upgrade to Premium" + "message": "Mettre à niveau vers Premium" }, "sessionTimeoutSettingsAction": { - "message": "Timeout action" + "message": "Action à l’expiration" }, "sessionTimeoutHeader": { - "message": "Session timeout" + "message": "Délai d'expiration de la session" } } From bc7825654b585b7963619bb335cb8dc9eee3d83b Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:45:19 +0100 Subject: [PATCH 201/249] Autosync the updated translations (#17531) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- apps/web/src/locales/af/messages.json | 15 ++++++-- apps/web/src/locales/ar/messages.json | 15 ++++++-- apps/web/src/locales/az/messages.json | 49 ++++++++++++++---------- apps/web/src/locales/be/messages.json | 15 ++++++-- apps/web/src/locales/bg/messages.json | 15 ++++++-- apps/web/src/locales/bn/messages.json | 15 ++++++-- apps/web/src/locales/bs/messages.json | 15 ++++++-- apps/web/src/locales/ca/messages.json | 15 ++++++-- apps/web/src/locales/cs/messages.json | 13 ++++++- apps/web/src/locales/cy/messages.json | 15 ++++++-- apps/web/src/locales/da/messages.json | 15 ++++++-- apps/web/src/locales/de/messages.json | 15 ++++++-- apps/web/src/locales/el/messages.json | 15 ++++++-- apps/web/src/locales/en_GB/messages.json | 21 +++++++--- apps/web/src/locales/en_IN/messages.json | 21 +++++++--- apps/web/src/locales/eo/messages.json | 15 ++++++-- apps/web/src/locales/es/messages.json | 15 ++++++-- apps/web/src/locales/et/messages.json | 15 ++++++-- apps/web/src/locales/eu/messages.json | 15 ++++++-- apps/web/src/locales/fa/messages.json | 15 ++++++-- apps/web/src/locales/fi/messages.json | 15 ++++++-- apps/web/src/locales/fil/messages.json | 15 ++++++-- apps/web/src/locales/fr/messages.json | 49 ++++++++++++++---------- apps/web/src/locales/gl/messages.json | 15 ++++++-- apps/web/src/locales/he/messages.json | 15 ++++++-- apps/web/src/locales/hi/messages.json | 15 ++++++-- apps/web/src/locales/hr/messages.json | 15 ++++++-- apps/web/src/locales/hu/messages.json | 13 ++++++- apps/web/src/locales/id/messages.json | 15 ++++++-- apps/web/src/locales/it/messages.json | 15 ++++++-- apps/web/src/locales/ja/messages.json | 15 ++++++-- apps/web/src/locales/ka/messages.json | 15 ++++++-- apps/web/src/locales/km/messages.json | 15 ++++++-- apps/web/src/locales/kn/messages.json | 15 ++++++-- apps/web/src/locales/ko/messages.json | 15 ++++++-- apps/web/src/locales/lv/messages.json | 15 ++++++-- apps/web/src/locales/ml/messages.json | 15 ++++++-- apps/web/src/locales/mr/messages.json | 15 ++++++-- apps/web/src/locales/my/messages.json | 15 ++++++-- apps/web/src/locales/nb/messages.json | 15 ++++++-- apps/web/src/locales/ne/messages.json | 15 ++++++-- apps/web/src/locales/nl/messages.json | 15 ++++++-- apps/web/src/locales/nn/messages.json | 15 ++++++-- apps/web/src/locales/or/messages.json | 15 ++++++-- apps/web/src/locales/pl/messages.json | 15 ++++++-- apps/web/src/locales/pt_BR/messages.json | 15 ++++++-- apps/web/src/locales/pt_PT/messages.json | 47 ++++++++++++++--------- apps/web/src/locales/ro/messages.json | 15 ++++++-- apps/web/src/locales/ru/messages.json | 13 ++++++- apps/web/src/locales/si/messages.json | 15 ++++++-- apps/web/src/locales/sk/messages.json | 15 ++++++-- apps/web/src/locales/sl/messages.json | 15 ++++++-- apps/web/src/locales/sr_CS/messages.json | 15 ++++++-- apps/web/src/locales/sr_CY/messages.json | 15 ++++++-- apps/web/src/locales/sv/messages.json | 15 ++++++-- apps/web/src/locales/ta/messages.json | 15 ++++++-- apps/web/src/locales/te/messages.json | 15 ++++++-- apps/web/src/locales/th/messages.json | 15 ++++++-- apps/web/src/locales/tr/messages.json | 15 ++++++-- apps/web/src/locales/uk/messages.json | 15 ++++++-- apps/web/src/locales/vi/messages.json | 15 ++++++-- apps/web/src/locales/zh_CN/messages.json | 23 +++++++---- apps/web/src/locales/zh_TW/messages.json | 49 ++++++++++++++---------- 63 files changed, 830 insertions(+), 263 deletions(-) diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json index 2fee7410e93..0aa86168472 100644 --- a/apps/web/src/locales/af/messages.json +++ b/apps/web/src/locales/af/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json index 7dbfa670741..9b8dde78dd6 100644 --- a/apps/web/src/locales/ar/messages.json +++ b/apps/web/src/locales/ar/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json index 9ff3fc46040..5181906dacc 100644 --- a/apps/web/src/locales/az/messages.json +++ b/apps/web/src/locales/az/messages.json @@ -94,7 +94,7 @@ "message": "İrəliləyişi izləmək üçün üzvlərə tapşırıqlar təyin edin" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Tətbiqləri incələyib kritik olaraq işarələdikdən sonra, üzvlərə parollarını dəyişməsi üçün tapşırıqlar təyin edin." }, "sendReminders": { "message": "Xatırlatma göndər" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Heç bir veri tapılmadı" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Access Intelligence-i istifadə etməyə başlamaq üçün təşkilatınızın giriş verilərini daxilə köçürün. Bunu etdikdən sonra, bunları edə biləcəksiniz:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Tətbiqləri kritik olaraq işarələmə" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Bu, əvvəlcə ən vacib tətbiqlərinizdəki riskləri xaric etməyinizə kömək edəcək." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Üzvlərin təhlükəsizliyini təkmilləşdirməsinə kömək" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Riskli üzvlərə kimlik məlumatlarını güncəlləməsi üçün təhlükəsizlik tapşırıqları təyin edin." }, "feature3Title": { - "message": "Monitor progress" + "message": "İrəliləyişin monitorinqi" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Təhlükəsizlik təkmilləşdirmələrini göstərmək üçün zamanla dəyişiklikləri izləyin." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Hesabat yaratma" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Hesabatın hazırlanmasına başlaya bilərsiniz. Hazırladıqdan sonra, bunları edə biləcəksiniz:" }, "noCriticalApplicationsTitle": { "message": "Heç bir tətbiqi kritik olaraq işarələməmisiniz" @@ -266,13 +266,13 @@ "message": "Riskli üzvlər" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Bu üzvlər, kritik tətbiqlər üçün müdafiəsiz elementlərə erişə bilər." }, "membersWithAtRiskPasswords": { "message": "Riskli parollara sahib üzvlər" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Təşkilatınızdakı üzvlərə, təhlükəsizlik boşluğu mövcud olan parolları dəyişdirməsi üçün bir tapşırıq təyin ediləcək. Üzvlər, Bitwarden brauzer uzantılarında bir bildiriş alacaq." }, "membersAtRiskCount": { "message": "$COUNT$ üzv risk altındadır", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Bu üzvlər, kritik tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edir." }, "atRiskMembersDescriptionNone": { "message": "Tətbiqlərə zəif, ifşa olunmuş və ya təkrar istifadə olunan parollarla giriş edən üzv yoxdur." @@ -344,7 +344,7 @@ "message": "İncələmə gözləyən müraciətlər" }, "newApplicationsCardTitle": { - "message": "Review new applications" + "message": "Yeni tətbiqləri incələ" }, "newApplicationsWithCount": { "message": "$COUNT$ yeni müraciət", @@ -386,7 +386,7 @@ "message": "Kritik tətbiqləri prioritetləşdir" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Təşkilatınız üçün ən kritik sayılan tətbiqləri seçin. Daha sonra, riskləri aradan qaldırmaları üçün üzvlərə təhlükəsizlik tapşırıqları təyin edə biləcəksiniz." }, "reviewNewApplications": { "message": "Yeni tətbiqlər incələ" @@ -5809,9 +5809,9 @@ "message": "Bütün elementlərin bir təşkilata məxsus olmasını tələb et və elementlərin hesab səviyyəsində saxlanılma seçimini ləğv et.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Bütün elementlər bir təşkilata məxsus olacaq və orada saxlanılacaq, bu da təşkilat üzrə kontrollar, görünürlük və hesabatları mümkün edəcək. İşə salındığı zaman, hər üzv üçün elementləri saxlaya biləcəyi ilkin bir kolleksiya mövcud olacaq. Daha ətraflı ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "daha ətraflı öyrənin", @@ -6668,7 +6668,7 @@ "message": "This option will save your members' encryption keys on their devices. If you choose this option, ensure that their devices are adequately protected." }, "learnMoreAboutDeviceProtection": { - "message": "Learn more about device protection" + "message": "Cihaz mühafizəsi barədə daha ətraflı" }, "sessionTimeoutConfirmationOnSystemLockTitle": { "message": "\"System lock\" will only apply to the browser and desktop app" @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Ödənişsiz Ailələr sınağını başlat" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Seyf vaxt bitmə əməliyyatınızı dəyişdirmək üçün bir kilid açma üsulu qurun." }, diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json index e562c39ea7f..59d5d49ab1d 100644 --- a/apps/web/src/locales/be/messages.json +++ b/apps/web/src/locales/be/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json index 1f897b5d74e..eb926015996 100644 --- a/apps/web/src/locales/bg/messages.json +++ b/apps/web/src/locales/bg/messages.json @@ -5809,9 +5809,9 @@ "message": "Изискване на всички елементи да бъдат притежавани от организация, премахвайки възможността за съхраняване на елементи в отделен акаунт.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Всички елементи ще се притежават от и съхраняват в организацията, което ще означава, че управлението, видимостта и докладите ще се извършват от организацията. Когато това и включено, всеки член ще може да съхранява елементите си в стандартна колекция. Научете повече относно управлението на ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "жизнения цикъл на данните за удостоверяване", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Започнете безплатния пробен период на Семейния план" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Задайте метод за отключване, за да може да промените действието при изтичане на времето за достъп до трезора." }, diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json index 67fddc7d6e0..300f0bcd8f3 100644 --- a/apps/web/src/locales/bn/messages.json +++ b/apps/web/src/locales/bn/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json index cae2a248d0a..3dc0ab7494c 100644 --- a/apps/web/src/locales/bs/messages.json +++ b/apps/web/src/locales/bs/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json index 77204835dbe..35db86e2b28 100644 --- a/apps/web/src/locales/ca/messages.json +++ b/apps/web/src/locales/ca/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json index ccb2172dbf6..5693a5fc824 100644 --- a/apps/web/src/locales/cs/messages.json +++ b/apps/web/src/locales/cs/messages.json @@ -5809,9 +5809,9 @@ "message": "Vyžaduje, aby byly všechny položky vlastněny organizací s odebráním volby ukládat položky na úrovni účtu.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { + "organizationDataOwnershipDescContent": { "message": "Všechny položky budou vlastněny a uloženy v organizaci, což umožní organizaci ovládání, viditelnost a hlášení. Pokud je zapnuto, bude k dispozici výchozí sbírka pro každého člena pro ukládání položek. Další informace o správě ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "životního cyklu pověření", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Zahájit bezplatnou zkušební verzi pro rodiny" }, + "blockClaimedDomainAccountCreation": { + "message": "Blokovat vytvoření účtu pro nárokované domény" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Zabrání uživatelům ve vytváření účtů mimo Vaši organizaci pomocí e-mailových adres z nárokovaných domén." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "Před aktivací této zásady musí být nárokována doména." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Nastavte metodu odemknutí, abyste změnili časový limit Vašeho trezoru." }, diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json index c140204619e..2b0222d7096 100644 --- a/apps/web/src/locales/cy/messages.json +++ b/apps/web/src/locales/cy/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json index cc2a58aa27d..dee490c39e9 100644 --- a/apps/web/src/locales/da/messages.json +++ b/apps/web/src/locales/da/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json index 073126da446..4a83f8becc8 100644 --- a/apps/web/src/locales/de/messages.json +++ b/apps/web/src/locales/de/messages.json @@ -5809,9 +5809,9 @@ "message": "Verlangen, dass alle Einträge Eigentum einer Organisation sein müssen, wodurch die Möglichkeit, Einträge auf Kontoebene zu speichern, entfällt.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Alle Einträge sind Eigentum der Organisation und werden in ihr gespeichert, was eine organisationsweite Kontrolle, Sichtbarkeit und Berichterstattung ermöglicht. Wenn diese Funktion aktiviert ist, steht jedem Mitglied eine Standardsammlung zur Verfügung, in der es Einträge speichern kann. Erfahre mehr über die Verwaltung des ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "Zugangsdaten-Lebenszyklus", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Kostenlose Families-Testversion starten" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Richte eine Entsperrmethode ein, um deine Aktion bei Tresor-Timeout zu ändern." }, diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json index 606d10fa600..5e937df4a21 100644 --- a/apps/web/src/locales/el/messages.json +++ b/apps/web/src/locales/el/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json index 2f40b6fa13d..7e0f79f9a11 100644 --- a/apps/web/src/locales/en_GB/messages.json +++ b/apps/web/src/locales/en_GB/messages.json @@ -182,7 +182,7 @@ "message": "No data found" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Import your organisation's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, "feature1Title": { "message": "Mark applications as critical" @@ -272,7 +272,7 @@ "message": "Members with at-risk passwords" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Members of your organisation will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -386,7 +386,7 @@ "message": "Prioritise critical applications" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Select which applications are most critical to your organisation. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organisation, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organisation, enabling organisation-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organisation, enabling organisation-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside your organisation using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json index 180b90cf878..864f66ade8e 100644 --- a/apps/web/src/locales/en_IN/messages.json +++ b/apps/web/src/locales/en_IN/messages.json @@ -182,7 +182,7 @@ "message": "No data found" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Import your organisation's login data to get started with Access Intelligence. Once you do that, you'll be able to:" }, "feature1Title": { "message": "Mark applications as critical" @@ -272,7 +272,7 @@ "message": "Members with at-risk passwords" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Members of your organisation will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." }, "membersAtRiskCount": { "message": "$COUNT$ members at-risk", @@ -386,7 +386,7 @@ "message": "Prioritise critical applications" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Select which applications are most critical to your organisation. Then, you’ll be able to assign security tasks to members to remove risks." }, "reviewNewApplications": { "message": "Review new applications" @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organisation, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organisation, enabling organisation-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organisation, enabling organisation-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside your organisation using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json index 94fd92f45c9..3ec660dc5b2 100644 --- a/apps/web/src/locales/eo/messages.json +++ b/apps/web/src/locales/eo/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json index be77b075923..b69c4ebf822 100644 --- a/apps/web/src/locales/es/messages.json +++ b/apps/web/src/locales/es/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json index 2dddfda1bb6..f96af492352 100644 --- a/apps/web/src/locales/et/messages.json +++ b/apps/web/src/locales/et/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json index 785ba6e2e0d..20aec32c796 100644 --- a/apps/web/src/locales/eu/messages.json +++ b/apps/web/src/locales/eu/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json index 71da2a1b8ba..56aa148b77a 100644 --- a/apps/web/src/locales/fa/messages.json +++ b/apps/web/src/locales/fa/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json index b735280c312..a1332427b09 100644 --- a/apps/web/src/locales/fi/messages.json +++ b/apps/web/src/locales/fi/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json index f91cf0d9bf0..9935386d012 100644 --- a/apps/web/src/locales/fil/messages.json +++ b/apps/web/src/locales/fil/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json index d97fca90d57..db3aa3090b1 100644 --- a/apps/web/src/locales/fr/messages.json +++ b/apps/web/src/locales/fr/messages.json @@ -94,7 +94,7 @@ "message": "Affecter des tâches aux membres pour surveiller la progression" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "Une fois que vous passez en revue les demandes et que vous les marquez comme critiques, attribuez des tâches à vos membres pour changer leurs mots de passe." }, "sendReminders": { "message": "Envoyer des rappels" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "Aucune donnée n’a été trouvée" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importez les données de connexion de votre organisation pour commencer avec Intelligence. Une fois que vous aurez fait cela, vous pourrez :" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Marquer les applications comme critiques" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Cela vous aidera à éliminer les risques pour vos applications les plus importantes en premier." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Aider les membres à améliorer leur sécurité" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Assigner des membres à risque des tâches de sécurité guidées pour mettre à jour les identifiants." }, "feature3Title": { - "message": "Monitor progress" + "message": "Suivre la progression" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Suivez les changements au fil du temps pour afficher les améliorations de sécurité." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Générer un rapport" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Vous êtes prêt à commencer à générer des rapports. Une fois que vous en aurez généré, vous pourrez :" }, "noCriticalApplicationsTitle": { "message": "Vous n'avez marqué aucune application comme critique" @@ -266,13 +266,13 @@ "message": "Membres à risque" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Ces membres ont accès à des éléments vulnérables pour des applications critiques." }, "membersWithAtRiskPasswords": { "message": "Membres avec des mots de passe à risque" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Les membres de votre organisation seront assignés à une tâche pour changer les mots de passe vulnérables. Ils recevront une notification dans leur extension de navigateur Bitwarden." }, "membersAtRiskCount": { "message": "$COUNT$ membres à risque", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Ces membres se connectent à des applications critiques avec des mots de passe faibles, exposés ou réutilisés." }, "atRiskMembersDescriptionNone": { "message": "Aucun membre ne se connecte dans des applications avec des mots de passe faibles, exposés ou réutilisés." @@ -386,13 +386,13 @@ "message": "Prioriser les applications critiques" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Sélectionnez les applications les plus importantes pour votre organisation. Ensuite, vous pourrez assigner des tâches de sécurité aux membres pour supprimer les risques." }, "reviewNewApplications": { "message": "Examiner les nouvelles applications" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Examinez les nouvelles applications avec des éléments vulnérables et marquez celles que vous souhaitez surveiller de près comme étant critiques. Ensuite, vous pourrez assigner des tâches de sécurité aux membres pour supprimer les risques." }, "clickIconToMarkAppAsCritical": { "message": "Cliquez sur l'icône étoile pour marquer une application comme critique" @@ -5809,9 +5809,9 @@ "message": "Exiger que tous les éléments appartiennent à une organisation, en supprimant la possibilité de sauvegarder des éléments au niveau du compte.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Tous les éléments seront détenus et enregistrés dans l'organisation, ce qui activera les contrôles, la visibilité et les rapports à l'échelle de l'organisation. Quand activé, une collection par défaut est disponible pour chaque membre pour enregistrer des éléments. En savoir plus sur la gestion de ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "Tous les éléments seront détenus et enregistrés dans l'organisation, ce qui activera les contrôles, la visibilité et les rapports à l'échelle de l'organisation. Une fois activé, une collection par défaut sera disponible pour chaque membre pour enregistrer des éléments. En savoir plus sur la gestion de ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "cycle de vie des identifiants", @@ -9851,7 +9851,7 @@ "message": "Assigner des tâches" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Envoyer des notifications pour modifier les mots de passe" }, "assignToCollections": { "message": "Assigner aux collections" @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Commencez l'essai gratuit au forfait Familles" }, + "blockClaimedDomainAccountCreation": { + "message": "Bloquer la création de compte pour les domaines réclamés" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Empêcher les utilisateurs de créer des comptes en dehors de votre organisation en utilisant les adresses courriel des domaines revendiqués." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "Un domaine doit être réclamé avant d'activer cette politique de sécurité." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configurez une méthode de déverrouillage pour changer le délai d'expiration de votre coffre." }, diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json index c9fc10749d7..5ff4117909b 100644 --- a/apps/web/src/locales/gl/messages.json +++ b/apps/web/src/locales/gl/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json index 5eb5c7969ee..f5f8bfd3c78 100644 --- a/apps/web/src/locales/he/messages.json +++ b/apps/web/src/locales/he/messages.json @@ -5809,9 +5809,9 @@ "message": "דרוש שכל הפריטים יהיו בבעלות ארגון, מה שמסיר את האפשרות לאחסן פריטים ברמת החשבון.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "כל הפריטים יהיו בבעלות הארגון ויישמרו בו, מה שמאפשר בקרות, נראות, ודיווח כלל־ארגוניות. כאשר אפשרות זו מופעלת, אוסף ברירת מחדל יהיה זמין עבור כל חבר כדי לאחסן פריטים. למד עוד על ניהול ה", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "מחזור החיים של אישורים", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "התחל ניסיון משפחות בחינם" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "הגדר שיטת ביטול נעילה כדי לשנות את פעולת פסק הזמן לכספת שלך." }, diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json index 766c6c3e8bd..b3ecb676b4d 100644 --- a/apps/web/src/locales/hi/messages.json +++ b/apps/web/src/locales/hi/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json index a0dbd290ab3..26a784acf5c 100644 --- a/apps/web/src/locales/hr/messages.json +++ b/apps/web/src/locales/hr/messages.json @@ -5809,9 +5809,9 @@ "message": "Zahtijevaj da su sve stavke u vlasništvu organizacije čime se onemogućuje spremanje stavki na osobnoj razini.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Sve će stavke biti u vlasništvu i spremljene u organizaciji, što će omogućiti kontrolu, vidljivost i izvještavanje na razini cijele organizacije. Kada je uključeno, svakom će članu biti dostupna zadana kolekcija za pohranu stavki. Saznaj više o upravljanju ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "životnim ciklusom vjerodajnica", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Započni besplatno probno razdoblje za Families" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Postavi metodu otključavanja za promjenu radnje nakon isteka vremenskog ograničenja trezora." }, diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json index df292a29c02..82a892eb094 100644 --- a/apps/web/src/locales/hu/messages.json +++ b/apps/web/src/locales/hu/messages.json @@ -5809,9 +5809,9 @@ "message": "Szükséges, hogy minden elem egy szervezet tulajdonában legyen, megújítva a fiókszintű elemek tárolásának lehetőségét.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { + "organizationDataOwnershipDescContent": { "message": "Minden elem a szervezet tulajdonába és elmentésre kerül, lehetővé téve az egész szervezetre kiterjedő vezérlést, láthatóságot és jelentést. Ha be van kapcsolva, minden tag számára elérhető egy alapértelmezett gyűjtemény az elemek tárolására. Tudjunk meg többet a kezelésről:", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "hitelesítő életciklus", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Az ingyenes Családok próbaverzió elindítása" }, + "blockClaimedDomainAccountCreation": { + "message": "Fiók létrehozásának letiltása az igényelt tartományokhoz" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Megakadályozza, hogy a felhasználók a szervezetén kívül fiókokat hozzanak létre az igényelt tartományokból származó email címek használatával." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A házirend aktiválása előtt egy tartományt igényelni kell." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Állítsunk be egy feloldási módot a széf időkifutási műveletének módosításához." }, diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json index 388ca3ba9cf..86b9356ac08 100644 --- a/apps/web/src/locales/id/messages.json +++ b/apps/web/src/locales/id/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json index b60e8f4584a..ffc82cfcb4f 100644 --- a/apps/web/src/locales/it/messages.json +++ b/apps/web/src/locales/it/messages.json @@ -5809,9 +5809,9 @@ "message": "Richiede che tutti gli elementi siano di proprietà di un'organizzazione. Disabilita per memorizzare gli elementi a livello di account personale.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Tutti gli elementi saranno posseduti e memorizzati dall'organizzazione, e saranno disponibili controlli, visibilità e reporting. Quando attivato, è disponibile una raccolta predefinita per ogni utente. Per sapere di più sulla gestione del ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "ciclo di vita delle credenziali", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json index eed4085320d..66f30f93f96 100644 --- a/apps/web/src/locales/ja/messages.json +++ b/apps/web/src/locales/ja/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json index b64d882bb03..c4069973d90 100644 --- a/apps/web/src/locales/ka/messages.json +++ b/apps/web/src/locales/ka/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json index 9f57ed8c6ca..b23e2e3cbac 100644 --- a/apps/web/src/locales/km/messages.json +++ b/apps/web/src/locales/km/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json index 41017933852..49d834e9cae 100644 --- a/apps/web/src/locales/kn/messages.json +++ b/apps/web/src/locales/kn/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json index 971a2ee59ae..33d812d1537 100644 --- a/apps/web/src/locales/ko/messages.json +++ b/apps/web/src/locales/ko/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json index ba978730477..9b1b54cf0b5 100644 --- a/apps/web/src/locales/lv/messages.json +++ b/apps/web/src/locales/lv/messages.json @@ -5809,9 +5809,9 @@ "message": "Visiem vienumiem jāpieder apvienībai, tādējādi noņemot iespēju glabāt tos konta līmenī.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Uzsākt ģimenes plāna bezmaksas izmēģinājumu" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Jāuzstāda atslēgšanas veids, lai mainītu glabātavas noildzes darbību." }, diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json index 14f5b921ce7..1632226b9ce 100644 --- a/apps/web/src/locales/ml/messages.json +++ b/apps/web/src/locales/ml/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json index a7a1e405ed7..b71d94fe31e 100644 --- a/apps/web/src/locales/mr/messages.json +++ b/apps/web/src/locales/mr/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json index 9f57ed8c6ca..b23e2e3cbac 100644 --- a/apps/web/src/locales/my/messages.json +++ b/apps/web/src/locales/my/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json index 345d875875c..33ff6b1fb58 100644 --- a/apps/web/src/locales/nb/messages.json +++ b/apps/web/src/locales/nb/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json index 8387c568e94..d87792ef4b5 100644 --- a/apps/web/src/locales/ne/messages.json +++ b/apps/web/src/locales/ne/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json index 35d61fc7ce3..0d8840a1889 100644 --- a/apps/web/src/locales/nl/messages.json +++ b/apps/web/src/locales/nl/messages.json @@ -5809,9 +5809,9 @@ "message": "Verplicht dat items eigendom zijn van een organisatie, dit verwijdert de mogelijkheid van het opslaan van items op accountniveau.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Alle items worden eigendom en opgeslagen bij de organisatie, wat organisatiebrede controle, zichtbaarheid en rapportage mogelijk maakt. Bij inschakelen is er een standaardcollectie beschikbaar voor elk lid voor het opslaan van items. Meer informatie over het beheren van de ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "Alle items worden eigendom van en opgeslagen bij de organisatie, wat organisatiebrede controle, zichtbaarheid en rapportage mogelijk maakt. Bij inschakelen is er een standaardcollectie beschikbaar voor elk lid voor het opslaan van items. Meer informatie over het beheren van de ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "levenscyclus van inloggegevens", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start gratis Families-proefperiode" }, + "blockClaimedDomainAccountCreation": { + "message": "Blokkeer het aanmaken van een account voor geclaimde domeinen" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Voorkom dat gebruikers buiten je organisatie accounts aanmaken met behulp van e-mailadressen van geclaimde domeinen." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "Voordat dit beleid geactiveerd kan worden, moet je een domein claimen." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Stel een ontgrendelingsmethode in om je kluis time-out actie te wijzigen." }, diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json index 4979a3eb72a..2777a14d5c7 100644 --- a/apps/web/src/locales/nn/messages.json +++ b/apps/web/src/locales/nn/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json index 9f57ed8c6ca..b23e2e3cbac 100644 --- a/apps/web/src/locales/or/messages.json +++ b/apps/web/src/locales/or/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json index 48f25c451ae..38b7fb51de3 100644 --- a/apps/web/src/locales/pl/messages.json +++ b/apps/web/src/locales/pl/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json index 4520e190b53..09dd1b14c4a 100644 --- a/apps/web/src/locales/pt_BR/messages.json +++ b/apps/web/src/locales/pt_BR/messages.json @@ -5809,9 +5809,9 @@ "message": "Exigir que todos os itens sejam propriedade de uma organização, removendo a opção de armazenar itens ao nível da conta.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Todos os itens serão propriedade e salvos na organização, ativando controles, visibilidade, e relatórios em toda a organização. Ao ativar, a coleção padrão estará disponível para que cada membro possa armazenar itens. Aprenda mais sobre gerenciar o", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "ciclo de vida credencial", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json index 4831f14d694..4e240bafc19 100644 --- a/apps/web/src/locales/pt_PT/messages.json +++ b/apps/web/src/locales/pt_PT/messages.json @@ -15,7 +15,7 @@ "message": "Não há aplicações críticas em risco" }, "accessIntelligence": { - "message": "Aceder à informação" + "message": "Inteligência de Acesso" }, "passwordRisk": { "message": "Risco da palavra-passe" @@ -182,31 +182,31 @@ "message": "Não foram encontrados dados" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "Importe os dados de início de sessão da sua organização para começar a utilizar a Inteligência de Acesso. Depois disso, poderá:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "Marcar aplicações como críticas" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "Isto ajudá-lo-á a eliminar primeiro os riscos nas suas aplicações mais importantes." }, "feature2Title": { - "message": "Help members improve their security" + "message": "Ajude os membros a melhorar a sua segurança" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "Atribua aos membros em risco tarefas de segurança orientadas para atualizarem as suas credenciais." }, "feature3Title": { - "message": "Monitor progress" + "message": "Monitorizar o progresso" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "Acompanhe as alterações ao longo do tempo para mostrar as melhorias na segurança." }, "noReportsRunTitle": { - "message": "Generate report" + "message": "Gerar relatório" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "Está pronto para começar a gerar relatórios. Depois de os gerar, poderá:" }, "noCriticalApplicationsTitle": { "message": "Não marcou nenhuma aplicação como crítica" @@ -266,13 +266,13 @@ "message": "Membros em risco" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "Estes membros têm acesso a itens vulneráveis de aplicações críticas." }, "membersWithAtRiskPasswords": { "message": "Membros com palavras-passe em risco" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "Os membros da sua organização serão atribuídos a uma tarefa para alterar palavras-passe vulneráveis. Receberão uma notificação na extensão do Bitwarden do navegador." }, "membersAtRiskCount": { "message": "$COUNT$ membros em risco", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "Estes membros estão a iniciar sessão em aplicações críticas com palavras-passe fracas, expostas ou reutilizadas." }, "atRiskMembersDescriptionNone": { "message": "Estes não são membros que iniciam sessão em aplicações com palavras-passe fracas, expostas ou reutilizadas." @@ -386,13 +386,13 @@ "message": "Dê prioridade a aplicações críticas" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Selecione quais aplicações são mais críticas para a sua organização. Depois, poderá atribuir tarefas de segurança aos membros para eliminar os riscos." }, "reviewNewApplications": { "message": "Rever novas aplicações" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "Reveja as novas aplicações com itens vulneráveis e marque como críticas aquelas que pretende monitorizar de perto. Depois, poderá atribuir tarefas de segurança aos membros para eliminar os riscos." }, "clickIconToMarkAppAsCritical": { "message": "Clique no ícone de estrela para marcar uma app como crítica" @@ -5809,9 +5809,9 @@ "message": "Exigir que todos os itens sejam propriedade de uma organização, removendo a opção de armazenar itens ao nível da conta.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Todos os itens pertencerão e serão guardados na organização, permitindo controlos, visibilidade e relatórios para toda a organização. Quando ativada, estará disponível uma coleção predefinida para cada membro armazenar itens. Saiba mais sobre como gerir o ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "Todos os itens passarão a ser propriedade da organização e serão aí guardados, permitindo controlos, visibilidade e relatórios a nível organizacional. Quando esta opção estiver ativa, ficará disponível uma coleção predefinida para cada membro guardar itens. Saiba mais sobre a gestão do ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "ciclo de vida das credenciais", @@ -9851,7 +9851,7 @@ "message": "Atribuir tarefas" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "Enviar notificações para alterar palavras-passe" }, "assignToCollections": { "message": "Atribuir às coleções" @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Comece o teste gratuito do plano Familiar" }, + "blockClaimedDomainAccountCreation": { + "message": "Bloquear a criação de contas para domínios reclamados" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Impedir que os utilizadores criem contas fora da sua organização utilizando endereços de e-mail de domínios reclamados." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "Um domínio tem de ser reclamado antes de ativar esta política." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Configure um método de desbloqueio para alterar a ação de tempo limite do seu cofre." }, diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json index 0c3eaa3ea08..5b03a1b7440 100644 --- a/apps/web/src/locales/ro/messages.json +++ b/apps/web/src/locales/ro/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json index 205e953c0ed..f2b9856e97a 100644 --- a/apps/web/src/locales/ru/messages.json +++ b/apps/web/src/locales/ru/messages.json @@ -5809,9 +5809,9 @@ "message": "Необходимо, чтобы все элементы принадлежали организации, что исключает возможность их хранения на уровне аккаунта.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { + "organizationDataOwnershipDescContent": { "message": "Все элементы будут принадлежать организации и сохраняться в ней, что позволит осуществлять контроль, видимость и отчетность в масштабах всей организации. При включении для каждого участника будет доступна коллекция по умолчанию для хранения элементов. Узнайте больше об управлении ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "жизненным циклом учетных данных", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Начать пробную версию Families" }, + "blockClaimedDomainAccountCreation": { + "message": "Блокировать создание аккаунта для заявленных доменов" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Запретите пользователям создавать аккаунты за пределами вашей организации, используя адреса email из заявленных доменов." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "Перед активацией этой политики необходимо заявить домен." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Настройте способ разблокировки для изменения действия по тайм-ауту хранилища." }, diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json index aeb1a4b4e0e..65838aec607 100644 --- a/apps/web/src/locales/si/messages.json +++ b/apps/web/src/locales/si/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json index 30afb3afcc2..933a3494404 100644 --- a/apps/web/src/locales/sk/messages.json +++ b/apps/web/src/locales/sk/messages.json @@ -5809,9 +5809,9 @@ "message": "Odstrániť možnosť uložiť položku pod kontom a požadovať aby všetky položky boli vlastnené organizáciou.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Všetky položky budú vo vlastníctve organizácie a uložené v nej, čo umožní kontrolu, prehľadnosť a vykazovanie v rámci celej organizácie. Po zapnutí bude každému členovi k dispozícii predvolená zbierka na ukladanie položiek. Dozvedieť sa viac o správe ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "životného cyklu položiek", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Začať bezplatnú skúšku pre predplatné Rodiny" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json index 2f14c42dd95..b1d5da3db34 100644 --- a/apps/web/src/locales/sl/messages.json +++ b/apps/web/src/locales/sl/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json index 6201f3a7e4e..9052035c174 100644 --- a/apps/web/src/locales/sr_CS/messages.json +++ b/apps/web/src/locales/sr_CS/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/sr_CY/messages.json b/apps/web/src/locales/sr_CY/messages.json index 1d0ae7b0897..c7dd63b5805 100644 --- a/apps/web/src/locales/sr_CY/messages.json +++ b/apps/web/src/locales/sr_CY/messages.json @@ -5809,9 +5809,9 @@ "message": "Захтевајте све ставке да буду у власништву организације, уклањајући опцију за чување предмета на нивоу рачуна.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json index 3ebe0499428..a90524b95bc 100644 --- a/apps/web/src/locales/sv/messages.json +++ b/apps/web/src/locales/sv/messages.json @@ -5809,9 +5809,9 @@ "message": "Kräv att alla objekt ägs av en organisation och ta bort möjligheten att lagra objekt på kontonivå.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Alla objekt kommer att ägas och sparas i organisationen, vilket möjliggör kontroll, synlighet och rapportering för hela organisationen. När funktionen är aktiverad finns en standardsamling tillgänglig för varje medlem att lagra objekt i. Läs mer om hur du hanterar ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "livscykel för inloggningsuppgifter", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Starta gratis testperiod för Families" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Konfigurera en upplåsningsmetod för att ändra tidsgränsåtgärden för valvet." }, diff --git a/apps/web/src/locales/ta/messages.json b/apps/web/src/locales/ta/messages.json index 64ce03adb11..eabda4c7611 100644 --- a/apps/web/src/locales/ta/messages.json +++ b/apps/web/src/locales/ta/messages.json @@ -5809,9 +5809,9 @@ "message": "கணக்கு மட்டத்தில் பொருட்களை சேமிக்கும் விருப்பத்தை நீக்கி, அனைத்து பொருட்களும் ஒரு நிறுவனத்திற்கு சொந்தமானதாக இருக்க வேண்டும்.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "அனைத்து பொருட்களும் நிறுவனத்திற்குச் சொந்தமானதாகவும், சேமிக்கப்படுவதாகவும் இருக்கும், இது நிறுவனம் முழுவதும் கட்டுப்பாடுகள், தெரிவுநிலை மற்றும் அறிக்கையிடலை செயல்படுத்துகிறது. இயக்கப்படும் போது, ஒவ்வொரு உறுப்பினருக்கும் பொருட்களைச் சேமிக்க ஒரு இயல்புநிலை கலெக்‌ஷன் கிடைக்கும். நிர்வகிப்பது பற்றி மேலும் அறிக ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "சான்றளிப்பு வாழ்க்கைச் சுழற்சி", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json index 9f57ed8c6ca..b23e2e3cbac 100644 --- a/apps/web/src/locales/te/messages.json +++ b/apps/web/src/locales/te/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json index 30a75f4d4da..bcaada3bea5 100644 --- a/apps/web/src/locales/th/messages.json +++ b/apps/web/src/locales/th/messages.json @@ -5809,9 +5809,9 @@ "message": "Require all items to be owned by an organization, removing the option to store items at the account level.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "credential lifecycle", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json index 448172f3c43..82ca8d37b61 100644 --- a/apps/web/src/locales/tr/messages.json +++ b/apps/web/src/locales/tr/messages.json @@ -5809,9 +5809,9 @@ "message": "Tüm kayıtların bir kuruluşa ait olmasını zorunlu kılın ve kayıtları hesap düzeyinde depolama seçeneğini kaldırın.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Tüm kayıtlar kuruluşun mülkiyetinde olacak ve kuruluşta saklanacak, böylece kuruluş genelinde kontrol, görünürlük ve raporlama sağlanacaktır. Etkinleştirildiğinde, her üyenin kayıtları depolaması için varsayılan bir koleksiyon kullanılabilir hale gelir. Yönetme hakkında daha fazla bilgi edinin ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "kimlik bilgisi yaşam döngüsü", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Kasa zaman aşımı eyleminizi değiştirmek için kilit açma yönteminizi ayarlayın." }, diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json index f1f7d6530b7..85fbbc317e7 100644 --- a/apps/web/src/locales/uk/messages.json +++ b/apps/web/src/locales/uk/messages.json @@ -5809,9 +5809,9 @@ "message": "Вимагати, щоб усіма записами володіла організація, вилучивши функцію збереження на рівні облікового запису.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Усі записи зберігатимуться в організації та належатимуть їй, забезпечуючи керування, доступність і звітування на рівні організації. Якщо ввімкнути цю функцію, кожен учасник зможе зберігати записи в типовій збірці. Докладніше про ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "життєвий цикл облікових даних", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Start free Families trial" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json index f05004b46da..b361fb88822 100644 --- a/apps/web/src/locales/vi/messages.json +++ b/apps/web/src/locales/vi/messages.json @@ -5809,9 +5809,9 @@ "message": "Yêu cầu tất cả các mục phải thuộc sở hữu của một tổ chức, loại bỏ tùy chọn lưu trữ mục ở cấp tài khoản.", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "Tất cả các mục sẽ thuộc sở hữu và được lưu vào tổ chức, cho phép kiểm soát, giám sát và báo cáo trên toàn tổ chức. Khi bật, một bộ sưu tập mặc định sẽ có sẵn cho mỗi thành viên để lưu trữ mục. Tìm hiểu thêm về quản lý ", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "vòng đời thông tin xác thực", @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "Bắt đầu dùng thử Gói Gia đình miễn phí" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "Set up an unlock method to change your vault timeout action." }, diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json index 05534b3a8b2..6ccfef71f97 100644 --- a/apps/web/src/locales/zh_CN/messages.json +++ b/apps/web/src/locales/zh_CN/messages.json @@ -194,7 +194,7 @@ "message": "帮助成员提升他们的安全性" }, "feature2Description": { - "message": "为存在风险的成员分配引导式安全任务,以更新凭证。" + "message": "为存在风险的成员分配指导性安全任务,以更新其凭据。" }, "feature3Title": { "message": "监测进度" @@ -266,7 +266,7 @@ "message": "存在风险的成员" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "这些成员拥有关键应用程序中易受攻击项目的访问权限。" + "message": "这些成员拥有对关键应用程序中易受攻击项目的访问权限。" }, "membersWithAtRiskPasswords": { "message": "密码存在风险的成员" @@ -392,7 +392,7 @@ "message": "审查新应用程序" }, "reviewNewAppsDescription": { - "message": "审查具有易受攻击项目的新应用程序然后将需要密切监测的应用程序标记为关键。然后,您可以将安全任务分配给成员以消除风险。" + "message": "审查具有易受攻击项目的新应用程序,并将需要密切监测的应用程序标记为关键。然后,您可以将安全任务分配给成员以消除风险。" }, "clickIconToMarkAppAsCritical": { "message": "点击星形图标以将 App 标记为关键" @@ -5809,9 +5809,9 @@ "message": "要求所有项目归组织所有,禁用此选项以在账户级别存储项目。", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "所有项目都将归组织所有并保存到组织中,从而实现整个组织范围内的控制、可见性和报告。开启后,每个成员都可以使用默认集合来存储项目。进一步了解如何管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "所有项目都将归组织所有并保存至组织,从而实现组织范围的控制、可见性和报告功能。启用后,每个成员都将拥有一个默认集合来存储项目。进一步了解", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "凭据的生命周期", @@ -9851,7 +9851,7 @@ "message": "分配任务" }, "assignSecurityTasksToMembers": { - "message": "发送通知以更改密码" + "message": "发送更改密码的通知" }, "assignToCollections": { "message": "分配到集合" @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "开始免费家庭版试用" }, + "blockClaimedDomainAccountCreation": { + "message": "阻止为已声明的域名创建账户" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "禁止用户使用已声明域名的电子邮件地址在组织外部创建账户。" + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "必须先声明域名,然后才能激活此策略。" + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "设置一个解锁方式以更改您的密码库超时动作。" }, diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json index 3036366816a..add3fa6a84d 100644 --- a/apps/web/src/locales/zh_TW/messages.json +++ b/apps/web/src/locales/zh_TW/messages.json @@ -94,7 +94,7 @@ "message": "指派成員任務以監控進度" }, "onceYouReviewApplications": { - "message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords." + "message": "當您審查應用程式並將其標記為關鍵後,可指派任務給成員以變更其密碼。" }, "sendReminders": { "message": "傳送提醒" @@ -179,34 +179,34 @@ } }, "noDataInOrgTitle": { - "message": "No data found" + "message": "找不到資料" }, "noDataInOrgDescription": { - "message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:" + "message": "匯入您組織的登入資料以開始使用存取智慧功能。完成後,您將能夠:" }, "feature1Title": { - "message": "Mark applications as critical" + "message": "將應用程式標記為關鍵" }, "feature1Description": { - "message": "This will help you remove risks to your most important applications first." + "message": "這將協助您優先消除最重要應用程式的風險。" }, "feature2Title": { - "message": "Help members improve their security" + "message": "協助成員提升其安全性" }, "feature2Description": { - "message": "Assign at-risk members guided security tasks to update credentials." + "message": "指派有風險的成員執行指導式安全任務以更新憑證。" }, "feature3Title": { - "message": "Monitor progress" + "message": "監控進展" }, "feature3Description": { - "message": "Track changes over time to show security improvements." + "message": "追蹤隨時間變化的狀況以顯示安全性改善。" }, "noReportsRunTitle": { - "message": "Generate report" + "message": "產生報告" }, "noReportsRunDescription": { - "message": "You’re ready to start generating reports. Once you generate, you’ll be able to:" + "message": "您已準備好開始產生報告。產生報告後,您將能夠:" }, "noCriticalApplicationsTitle": { "message": "您尚未將任何應用程式標記為關鍵" @@ -266,13 +266,13 @@ "message": "具有風險的成員" }, "membersWithAccessToAtRiskItemsForCriticalApplications": { - "message": "These members have access to vulnerable items for critical applications." + "message": "這些成員可存取關鍵應用程式中的高風險項目。" }, "membersWithAtRiskPasswords": { "message": "使用有風險密碼的成員" }, "membersWillReceiveSecurityTask": { - "message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension." + "message": "您組織的成員將被指派變更高風險密碼的任務。他們會在 Bitwarden 瀏覽器擴充套件中收到通知。" }, "membersAtRiskCount": { "message": "$COUNT$ 位有風險的成員", @@ -302,7 +302,7 @@ } }, "atRiskMemberDescription": { - "message": "These members are logging into critical applications with weak, exposed, or reused passwords." + "message": "這些成員正以薄弱、已外洩或重複使用的密碼登入關鍵應用程式。" }, "atRiskMembersDescriptionNone": { "message": "目前沒有成員使用弱密碼、外洩密碼或重複密碼登入應用程式。" @@ -386,13 +386,13 @@ "message": "優先處理關鍵應用程式" }, "selectCriticalAppsDescription": { - "message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "選擇哪些應用程式對您的組織最為關鍵。之後,您將能指派安全任務給成員以消除風險。" }, "reviewNewApplications": { "message": "審查新應用程式" }, "reviewNewAppsDescription": { - "message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks." + "message": "審查包含高風險項目的新應用程式,並將您希望密切監控的項目標記為關鍵。之後,您將能指派安全任務給成員以消除風險。" }, "clickIconToMarkAppAsCritical": { "message": "點擊星形圖示以將應用程式標記為關鍵" @@ -5809,9 +5809,9 @@ "message": "要求所有項目由組織擁有,移除在帳號層級儲存項目的選項。", "description": "This is the policy description shown in the policy list." }, - "organizationDataOwnershipContent": { - "message": "所有項目將由組織擁有並儲存,啟用組織範圍的控管、可見性及報告。啟用後,每位成員將有預設集合可用於儲存項目。瞭解更多關於管理", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection be available for each member to store items. Learn more about managing the credential lifecycle.'" + "organizationDataOwnershipDescContent": { + "message": "All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the ", + "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'All items will be owned and saved to the organization, enabling organization-wide controls, visibility, and reporting. When turned on, a default collection will be available for each member to store items. Learn more about managing the credential lifecycle.'" }, "organizationDataOwnershipContentAnchor": { "message": "憑證生命週期", @@ -9851,7 +9851,7 @@ "message": "指派任務" }, "assignSecurityTasksToMembers": { - "message": "Send notifications to change passwords" + "message": "傳送變更密碼的通知" }, "assignToCollections": { "message": "指派至集合" @@ -12118,6 +12118,15 @@ "startFreeFamiliesTrial": { "message": "開始免費家庭試用" }, + "blockClaimedDomainAccountCreation": { + "message": "Block account creation for claimed domains" + }, + "blockClaimedDomainAccountCreationDesc": { + "message": "Prevent users from creating accounts outside of your organization using email addresses from claimed domains." + }, + "blockClaimedDomainAccountCreationPrerequisite": { + "message": "A domain must be claimed before activating this policy." + }, "unlockMethodNeededToChangeTimeoutActionDesc": { "message": "設定一個解鎖方式來變更您的密碼庫逾時動作。" }, From d98234c91f4a301b9a0fcab2911790af49dfb341 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:25:07 +0000 Subject: [PATCH 202/249] [PM-28558] [deps]: Update actions/upload-artifact action to v5.0.0 (#17538) * [deps]: Update actions/upload-artifact action to v4.6.2 * Bumping to 5.0.0. to keep all usages in sync All other upload-artifacts actions were also bumped to 5.0.0 with https://github.com/bitwarden/clients/pull/17305 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> --- .github/workflows/build-browser.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 772cb25d98d..3990a8bef95 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -349,7 +349,7 @@ jobs: - name: Upload dev extension artifact if: ${{ matrix.browser.archive_name_dev != '' }} - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: ${{ matrix.license_type.artifact_prefix }}${{ matrix.browser.artifact_name_dev }}-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/${{matrix.license_type.archive_name_prefix}}${{ matrix.browser.archive_name_dev }} From 678e5193cac7724832635f0b0f5006edf2da0e4e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:32:36 +0100 Subject: [PATCH 203/249] [deps]: Update Rust crate async-trait to v0.1.89 (#17570) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/chromium_importer/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index e8cc9385bb2..fc3e1458429 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -317,9 +317,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/desktop_native/chromium_importer/Cargo.toml b/apps/desktop/desktop_native/chromium_importer/Cargo.toml index 4b02079bfdb..9e9a9e0fee8 100644 --- a/apps/desktop/desktop_native/chromium_importer/Cargo.toml +++ b/apps/desktop/desktop_native/chromium_importer/Cargo.toml @@ -8,7 +8,7 @@ publish = { workspace = true } [dependencies] aes = { workspace = true } anyhow = { workspace = true } -async-trait = "=0.1.88" +async-trait = "=0.1.89" dirs = { workspace = true } hex = { workspace = true } rand = { workspace = true } From f6eb0f642e53f306c0d639d8c3b1de8dee2cfa12 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:52:51 +0100 Subject: [PATCH 204/249] [deps]: Update Rust crate clap to v4.5.51 (#17572) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 12 ++++++------ .../bitwarden_chromium_import_helper/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index fc3e1458429..a6bf229c138 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.40" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -647,9 +647,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.40" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -659,9 +659,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.40" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", diff --git a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml index 576a7d048fc..6455142023a 100644 --- a/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml +++ b/apps/desktop/desktop_native/bitwarden_chromium_import_helper/Cargo.toml @@ -11,7 +11,7 @@ publish.workspace = true aes-gcm = { workspace = true } chacha20poly1305 = { workspace = true } chromium_importer = { path = "../chromium_importer" } -clap = { version = "=4.5.40", features = ["derive"] } +clap = { version = "=4.5.51", features = ["derive"] } scopeguard = { workspace = true } sysinfo = { workspace = true } windows = { workspace = true, features = [ From 87e49c174cc7a80cfc69f068c7e09ec5e106e93b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:56:54 +0100 Subject: [PATCH 205/249] [deps] Platform: Update Rust crate libc to v0.2.177 (#17549) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 4 ++-- apps/desktop/desktop_native/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index a6bf229c138..7725abe43fb 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1666,9 +1666,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libloading" diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index d7afd44e9cd..864b743962d 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -39,7 +39,7 @@ futures = "=0.3.31" hex = "=0.4.3" homedir = "=0.3.4" interprocess = "=2.2.1" -libc = "=0.2.172" +libc = "=0.2.177" linux-keyutils = "=0.2.4" memsec = "=0.7.0" napi = "=2.16.17" From e9f67f4fd614bada0c4a8a214e4744c60b3da2b8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:57:31 +0100 Subject: [PATCH 206/249] [deps]: Update Rust crate cc to v1.2.46 (#17571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García <dani-garcia@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 11 +++++++++-- apps/desktop/desktop_native/objc/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 7725abe43fb..475253f935f 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -555,10 +555,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.4" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -1203,6 +1204,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "fixedbitset" version = "0.4.2" diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index c161b8226ba..5ef791fb586 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -14,7 +14,7 @@ tokio = { workspace = true } tracing = { workspace = true } [target.'cfg(target_os = "macos")'.build-dependencies] -cc = "=1.2.4" +cc = "=1.2.46" glob = "=0.3.2" [lints] From 994077f4de3fe99aa486ba7e8c33a0b97ca1259d Mon Sep 17 00:00:00 2001 From: Leslie Tilton <23057410+Banrion@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:12:48 -0600 Subject: [PATCH 207/249] [PM-28451] Fix icons in application review table (#17512) * Fix icons in application review table * Add default icon if none is found in review applications table. Move function to computed signal * Rename function * Remove redundant if statement --- .../new-applications-dialog.component.html | 2 +- .../new-applications-dialog.component.ts | 32 ++++++++++++++++--- .../review-applications-view.component.html | 6 +++- .../review-applications-view.component.ts | 7 +++- .../app-table-row-scrollable.component.html | 1 - .../app-table-row-scrollable.component.ts | 4 ++- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html index 04db5f6d521..2742dfdd8cb 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html @@ -28,7 +28,7 @@ </div> <dirt-review-applications-view - [applications]="getApplications()" + [applications]="applicationsWithIcons()" [selectedApplications]="selectedApplications()" (onToggleSelection)="toggleSelection($event)" (onToggleAll)="toggleAll()" diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts index 8655baccda3..4de8ecd9cd0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts @@ -33,6 +33,7 @@ import { } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { CipherIcon } from "../../shared/app-table-row-scrollable.component"; import { AccessIntelligenceSecurityTasksService } from "../../shared/security-tasks.service"; import { AssignTasksViewComponent } from "./assign-tasks-view.component"; @@ -99,6 +100,16 @@ export class NewApplicationsDialogComponent { // Applications selected to save as critical applications protected readonly selectedApplications = signal<Set<string>>(new Set()); + protected readonly applicationIcons = signal<Map<string, CipherIcon>>( + new Map<string, CipherIcon>(), + ); + protected readonly applicationsWithIcons = computed(() => { + return this.dialogParams.newApplications.map((app) => { + const iconCipher = this.applicationIcons().get(app.applicationName); + return { ...app, iconCipher } as ApplicationHealthReportDetail & { iconCipher: CipherIcon }; + }); + }); + // Used to determine if there are unassigned at-risk cipher IDs private readonly _tasks!: Signal<SecurityTask[]>; @@ -150,6 +161,7 @@ export class NewApplicationsDialogComponent { private securityTasksService: AccessIntelligenceSecurityTasksService, private toastService: ToastService, ) { + this.setApplicationIconMap(this.dialogParams.newApplications); // Setup the _tasks signal by manually passing in the injector this._tasks = toSignal(this.securityTasksService.tasks$, { initialValue: [], @@ -172,10 +184,6 @@ export class NewApplicationsDialogComponent { ); } - getApplications() { - return this.dialogParams.newApplications; - } - /** * Returns true if the organization has no existing critical applications. * Used to conditionally show different titles and descriptions. @@ -184,6 +192,22 @@ export class NewApplicationsDialogComponent { return !this.dialogParams.hasExistingCriticalApplications; } + /** + * Maps applications to a corresponding iconCipher + * + * @param applications + */ + setApplicationIconMap(applications: ApplicationHealthReportDetail[]) { + // Map the report data to include the iconCipher for each application + const iconCiphers = new Map<string, CipherIcon>(); + applications.forEach((app) => { + const iconCipher = + app.cipherIds.length > 0 ? this.dataService.getCipherIcon(app.cipherIds[0]) : undefined; + iconCiphers.set(app.applicationName, iconCipher); + }); + this.applicationIcons.set(iconCiphers); + } + /** * Toggles the selection state of an application. * @param applicationName The application to toggle diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html index 244cf2c5931..99053cbf94d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html @@ -62,7 +62,11 @@ </td> <td bitTypography="body1" class="tw-py-3 tw-px-2"> <div class="tw-flex tw-items-center tw-gap-2"> - <i class="bwi bwi-globe tw-text-muted" aria-hidden="true"></i> + @if (app.iconCipher) { + <app-vault-icon [cipher]="app.iconCipher"></app-vault-icon> + } @else { + <i class="bwi bwi-globe tw-text-muted" aria-hidden="true"></i> + } <span>{{ app.applicationName }}</span> </div> </td> diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.ts index 7a269d3aa15..172a4e01579 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.ts @@ -5,6 +5,9 @@ import { FormsModule } from "@angular/forms"; import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { ButtonModule, DialogModule, SearchModule, TypographyModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +import { CipherIcon } from "../../shared/app-table-row-scrollable.component"; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -18,10 +21,12 @@ import { I18nPipe } from "@bitwarden/ui-common"; SearchModule, TypographyModule, I18nPipe, + SharedModule, ], }) export class ReviewApplicationsViewComponent { - readonly applications = input.required<ApplicationHealthReportDetail[]>(); + readonly applications = + input.required<Array<ApplicationHealthReportDetail & { iconCipher: CipherIcon }>>(); readonly selectedApplications = input.required<Set<string>>(); protected readonly searchText = signal<string>(""); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html index edd90eaf97d..76a03e0c525 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.html @@ -45,7 +45,6 @@ tabindex="0" [attr.aria-label]="'viewItem' | i18n" > - <!-- Passing the first cipher of the application for app-vault-icon cipher input requirement --> <app-vault-icon *ngIf="row.iconCipher" [cipher]="row.iconCipher"></app-vault-icon> </td> <td diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts index 87e58ace898..73e3b347936 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/shared/app-table-row-scrollable.component.ts @@ -8,8 +8,10 @@ import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components" import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; +export type CipherIcon = CipherViewLike | undefined; + export type ApplicationTableDataSource = ApplicationHealthReportDetailEnriched & { - iconCipher: CipherViewLike | undefined; + iconCipher: CipherIcon; }; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush From db9b94e6a8231f313928c6ac0bf24957e17eb32d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:26:06 +0100 Subject: [PATCH 208/249] [deps] Platform: Update nx monorepo to v21.6.8 (#16415) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 1096 +++++++++++++++++++++++++-------------------- package.json | 12 +- 2 files changed, 620 insertions(+), 488 deletions(-) diff --git a/package-lock.json b/package-lock.json index dbb3fdb7e2d..d6883a3405e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,11 +32,11 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "14.9.0", - "@nx/devkit": "21.3.11", - "@nx/eslint": "21.3.11", - "@nx/jest": "21.3.11", - "@nx/js": "21.3.11", - "@nx/webpack": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/eslint": "21.6.8", + "@nx/jest": "21.6.8", + "@nx/js": "21.6.8", + "@nx/webpack": "21.6.8", "big-integer": "1.6.52", "braintree-web-drop-in": "1.44.0", "buffer": "6.0.3", @@ -159,7 +159,7 @@ "json5": "2.2.3", "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", - "nx": "21.3.11", + "nx": "21.6.8", "postcss": "8.5.3", "postcss-loader": "8.1.1", "prettier": "3.6.2", @@ -2906,14 +2906,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -3025,9 +3025,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3057,25 +3057,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -4535,17 +4535,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -4553,13 +4553,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -4569,13 +4569,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -7679,9 +7679,9 @@ } }, "node_modules/@jest/get-type": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", - "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "license": "MIT", "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -7828,12 +7828,12 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", - "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -7855,9 +7855,9 @@ } }, "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -7873,9 +7873,9 @@ } }, "node_modules/@jest/snapshot-utils/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "license": "MIT" }, "node_modules/@jest/source-map": { @@ -7986,6 +7986,16 @@ "@jridgewell/trace-mapping": "^0.3.24" } }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -9393,9 +9403,9 @@ } }, "node_modules/@nx/devkit": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.3.11.tgz", - "integrity": "sha512-JOV8TAa9K5+ZwTA/EUi0g5qcKEg5vmi0AyOUsrNUHlv3BgQnwZtPLDDTPPZ+ezq24o6YzgwueZWj3CLEdMHEDg==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-21.6.8.tgz", + "integrity": "sha512-N0cj0NqdxY2pcI0IJV+fAu362B6tppdv2ohSBNGacNeSqxfAlJxO5TFZePDmxX5nt0t9hAqT+iasfu4BSYGfZw==", "license": "MIT", "dependencies": { "ejs": "^3.1.7", @@ -9403,12 +9413,11 @@ "ignore": "^5.0.4", "minimatch": "9.0.3", "semver": "^7.5.3", - "tmp": "~0.2.1", "tslib": "^2.3.0", "yargs-parser": "21.1.1" }, "peerDependencies": { - "nx": "21.3.11" + "nx": ">= 20 <= 22" } }, "node_modules/@nx/devkit/node_modules/ignore": { @@ -9421,16 +9430,16 @@ } }, "node_modules/@nx/eslint": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-21.3.11.tgz", - "integrity": "sha512-9jeD8QuU3OMcItjtw0QHl5cwohLeA9R+lajNJoOjS2tUGXTHWb8NOcEZBXWMcML+eV1iloIDW8/P4jV4BYqP2w==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-21.6.8.tgz", + "integrity": "sha512-dloTsg1n1zlAyP2Ohwiw9vhoUrF5XYo5pdmxAmnpE/P+e4ihWQfGtlu3JvEQNBp9Wwk4E7XkJVmVhJOm47PIww==", "license": "MIT", "dependencies": { - "@nx/devkit": "21.3.11", - "@nx/js": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/js": "21.6.8", "semver": "^7.5.3", "tslib": "^2.3.0", - "typescript": "~5.8.2" + "typescript": "~5.9.2" }, "peerDependencies": { "@zkochan/js-yaml": "0.0.7", @@ -9442,16 +9451,29 @@ } } }, + "node_modules/@nx/eslint/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@nx/jest": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-21.3.11.tgz", - "integrity": "sha512-PkdNWeoUY81zr+jtUapBdvvh26lWYIhDNyUwTjIBFajX8EAlhJpvShKHs7QObmrwOMLMXwLHKINiSCw9rueOBQ==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-21.6.8.tgz", + "integrity": "sha512-2RpDTbh6Q/sp0ryxJmi4oY8xOaR4Us6gc91BzTFYSrzG0cX4Q/lFTiIAbV8bXY1UTJ5HMgJobHAy5Sbcfo6TeQ==", "license": "MIT", "dependencies": { "@jest/reporters": "^30.0.2", "@jest/test-result": "^30.0.2", - "@nx/devkit": "21.3.11", - "@nx/js": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/js": "21.6.8", "@phenomnomnominal/tsquery": "~5.0.1", "identity-obj-proxy": "3.0.0", "jest-config": "^30.0.2", @@ -9466,21 +9488,21 @@ } }, "node_modules/@nx/jest/node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -9505,13 +9527,13 @@ } }, "node_modules/@nx/jest/node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -9521,16 +9543,16 @@ } }, "node_modules/@nx/jest/node_modules/@jest/console": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", - "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.5", - "jest-util": "30.0.5", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -9538,88 +9560,88 @@ } }, "node_modules/@nx/jest/node_modules/@jest/environment": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", - "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.0.5", - "@jest/types": "30.0.5", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5" + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/@jest/expect": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", - "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "license": "MIT", "dependencies": { - "expect": "30.0.5", - "jest-snapshot": "30.0.5" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/@jest/expect-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", - "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1" + "@jest/get-type": "30.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/@jest/fake-timers": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", - "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/@jest/globals": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", - "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.5", - "@jest/expect": "30.0.5", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/@jest/reporters": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", - "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.5", - "@jest/test-result": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -9632,9 +9654,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.5", - "jest-util": "30.0.5", - "jest-worker": "30.0.5", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -9678,13 +9700,13 @@ } }, "node_modules/@nx/jest/node_modules/@jest/test-result": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", - "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.5", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -9693,14 +9715,14 @@ } }, "node_modules/@nx/jest/node_modules/@jest/test-sequencer": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", - "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.5", + "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -9708,22 +9730,22 @@ } }, "node_modules/@nx/jest/node_modules/@jest/transform": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", - "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", + "jest-haste-map": "30.2.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -9734,9 +9756,9 @@ } }, "node_modules/@nx/jest/node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "license": "MIT", "dependencies": { "@jest/pattern": "30.0.1", @@ -9752,9 +9774,9 @@ } }, "node_modules/@nx/jest/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "license": "MIT" }, "node_modules/@nx/jest/node_modules/@sinonjs/fake-timers": { @@ -9779,15 +9801,15 @@ } }, "node_modules/@nx/jest/node_modules/babel-jest": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", - "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "license": "MIT", "dependencies": { - "@jest/transform": "30.0.5", + "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -9796,14 +9818,17 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/@nx/jest/node_modules/babel-plugin-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", - "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -9816,13 +9841,11 @@ } }, "node_modules/@nx/jest/node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -9830,19 +9853,19 @@ } }, "node_modules/@nx/jest/node_modules/babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/@nx/jest/node_modules/camelcase": { @@ -9858,9 +9881,9 @@ } }, "node_modules/@nx/jest/node_modules/ci-info": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", - "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "funding": [ { "type": "github", @@ -9873,9 +9896,9 @@ } }, "node_modules/@nx/jest/node_modules/cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.1.tgz", + "integrity": "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ==", "license": "MIT" }, "node_modules/@nx/jest/node_modules/convert-source-map": { @@ -9885,26 +9908,26 @@ "license": "MIT" }, "node_modules/@nx/jest/node_modules/expect": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", - "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.5", - "@jest/get-type": "30.0.1", - "jest-matcher-utils": "30.0.5", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -9966,28 +9989,28 @@ } }, "node_modules/@nx/jest/node_modules/jest-circus": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", - "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.5", - "@jest/expect": "30.0.5", - "@jest/test-result": "30.0.5", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.5", - "jest-matcher-utils": "30.0.5", - "jest-message-util": "30.0.5", - "jest-runtime": "30.0.5", - "jest-snapshot": "30.0.5", - "jest-util": "30.0.5", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -9997,33 +10020,33 @@ } }, "node_modules/@nx/jest/node_modules/jest-config": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", - "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.0.1", + "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.0.5", - "@jest/types": "30.0.5", - "babel-jest": "30.0.5", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.5", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.5", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.5", - "jest-runner": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.0.5", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -10047,25 +10070,10 @@ } } }, - "node_modules/@nx/jest/node_modules/jest-diff": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", - "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", - "license": "MIT", - "dependencies": { - "@jest/diff-sequences": "30.0.1", - "@jest/get-type": "30.0.1", - "chalk": "^4.1.2", - "pretty-format": "30.0.5" - }, - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/@nx/jest/node_modules/jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "license": "MIT", "dependencies": { "detect-newline": "^3.1.0" @@ -10075,53 +10083,53 @@ } }, "node_modules/@nx/jest/node_modules/jest-each": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", - "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.5", + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", "chalk": "^4.1.2", - "jest-util": "30.0.5", - "pretty-format": "30.0.5" + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/jest-environment-node": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", - "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.5", - "@jest/fake-timers": "30.0.5", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.0.5" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/jest-haste-map": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", - "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "jest-worker": "30.0.5", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -10133,46 +10141,46 @@ } }, "node_modules/@nx/jest/node_modules/jest-leak-detector": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", - "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "pretty-format": "30.0.5" + "@jest/get-type": "30.1.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/jest-matcher-utils": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", - "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", + "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.0.5", - "pretty-format": "30.0.5" + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/jest-message-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", - "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -10181,14 +10189,14 @@ } }, "node_modules/@nx/jest/node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "30.0.5" + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -10204,17 +10212,17 @@ } }, "node_modules/@nx/jest/node_modules/jest-resolve": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", - "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", + "jest-haste-map": "30.2.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.5", - "jest-validate": "30.0.5", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -10223,31 +10231,31 @@ } }, "node_modules/@nx/jest/node_modules/jest-runner": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", - "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "license": "MIT", "dependencies": { - "@jest/console": "30.0.5", - "@jest/environment": "30.0.5", - "@jest/test-result": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.0.5", - "jest-haste-map": "30.0.5", - "jest-leak-detector": "30.0.5", - "jest-message-util": "30.0.5", - "jest-resolve": "30.0.5", - "jest-runtime": "30.0.5", - "jest-util": "30.0.5", - "jest-watcher": "30.0.5", - "jest-worker": "30.0.5", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -10256,31 +10264,31 @@ } }, "node_modules/@nx/jest/node_modules/jest-runtime": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", - "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "license": "MIT", "dependencies": { - "@jest/environment": "30.0.5", - "@jest/fake-timers": "30.0.5", - "@jest/globals": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.5", - "jest-message-util": "30.0.5", - "jest-mock": "30.0.5", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.0.5", - "jest-snapshot": "30.0.5", - "jest-util": "30.0.5", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -10289,9 +10297,9 @@ } }, "node_modules/@nx/jest/node_modules/jest-snapshot": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", - "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", @@ -10299,20 +10307,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.5", - "@jest/get-type": "30.0.1", - "@jest/snapshot-utils": "30.0.5", - "@jest/transform": "30.0.5", - "@jest/types": "30.0.5", - "babel-preset-current-node-syntax": "^1.1.0", + "@jest/expect-utils": "30.2.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.0.5", + "expect": "30.2.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.5", - "jest-matcher-utils": "30.0.5", - "jest-message-util": "30.0.5", - "jest-util": "30.0.5", - "pretty-format": "30.0.5", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -10321,12 +10329,12 @@ } }, "node_modules/@nx/jest/node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "license": "MIT", "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -10338,35 +10346,35 @@ } }, "node_modules/@nx/jest/node_modules/jest-validate": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", - "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.1", - "@jest/types": "30.0.5", + "@jest/get-type": "30.1.0", + "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@nx/jest/node_modules/jest-watcher": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", - "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.5", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "string-length": "^4.0.2" }, "engines": { @@ -10374,14 +10382,14 @@ } }, "node_modules/@nx/jest/node_modules/jest-worker": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", - "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -10412,9 +10420,9 @@ } }, "node_modules/@nx/jest/node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "license": "MIT", "dependencies": { "@jest/schemas": "30.0.5", @@ -10495,9 +10503,9 @@ } }, "node_modules/@nx/js": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/js/-/js-21.3.11.tgz", - "integrity": "sha512-aN8g1TP3FMN6MFLvMrZNaoqSwAkBFH1PunKQV17w4nlPkimWICaCP2DhY5W3VoOpjQBbhQoqrRt4mVfgnEpyvA==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-21.6.8.tgz", + "integrity": "sha512-I9Wu//28DXiSIFoW1IGbLRRfULITkYMOKGMnityQ71iJ8+9vi90a8xFyCWt4VzNxpMEreaoNynWJTf+I8UTGaw==", "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", @@ -10507,8 +10515,8 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", - "@nx/devkit": "21.3.11", - "@nx/workspace": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/workspace": "21.6.8", "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^3.1.0", @@ -10647,9 +10655,9 @@ } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.3.11.tgz", - "integrity": "sha512-qXZrW6kfsfGG9n4cWugR2v8ys7P1SsbQuFahlbNSTd7g+ZxozaOnc7tyxW9XuY84KQ35HwP/QSu1E13fK5CXwQ==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-21.6.8.tgz", + "integrity": "sha512-MG5bhSYhG49r+e0mLuztQVHz1sEy7cs8BvZJ56mm7JNQ1VfbaTyLAR7VcNw8O/1mJA8jYcg9fmxOIZOUnY1cEQ==", "cpu": [ "arm64" ], @@ -10660,9 +10668,9 @@ ] }, "node_modules/@nx/nx-darwin-x64": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.3.11.tgz", - "integrity": "sha512-6NJEIGRITpFZYptJtr/wdnVuidAS/wONMMSwX5rgAqh5A9teI0vxZVOgG6n5f6NQyqEDvZ9ytcIvLsQWA4kJFg==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-21.6.8.tgz", + "integrity": "sha512-e9oXVTd6U6RFEc3k22PGoe5OSy40fyyytl+svSJQmK+5rxZalvAUna/mXtFcQC9m6No2daqqpfAZlyN5nG6WHw==", "cpu": [ "x64" ], @@ -10673,9 +10681,9 @@ ] }, "node_modules/@nx/nx-freebsd-x64": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.3.11.tgz", - "integrity": "sha512-9VZOM9mutzuZCUgijHXrIl3NgKt2CWuH/awLqDS8ijhLs6WfI5TYTa+mFwx90dfZZ4y/jy6XWXa2Ee3OShf7Hg==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-21.6.8.tgz", + "integrity": "sha512-gK3rsTXQj0hHCyaAvZmPZeCPkb3jzesgtkXuZf+7pCm5b4wiEA1i22ufp1UzwaTmWgbub/6NVMEDOGsVcay8mA==", "cpu": [ "x64" ], @@ -10686,9 +10694,9 @@ ] }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.3.11.tgz", - "integrity": "sha512-a05tAySKDEWt0TGoSnWp/l5+HL/CDJQkHfI9pXho85oDSkVRzhOInAn1EeZB/F+Q3PnJFsMHMhbuu2/nm3uYJA==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-21.6.8.tgz", + "integrity": "sha512-TXt3HTFhM4kuL9larxBuo3XpSngoA1JCtHavfYLRC3A8knACi7LwNQwnF5RWAcYgKMVE3/8IAhV8LuemXrPKKw==", "cpu": [ "arm" ], @@ -10699,9 +10707,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.3.11.tgz", - "integrity": "sha512-MPeivf0ptNpzQYvww6zHIqVbE5dTT2isl/WqzGyy7NgSeYDpFXmouDCQaeKxo5WytMVRCvCw/NnWTQuCK6TjnA==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-21.6.8.tgz", + "integrity": "sha512-QwZREYIhqhDVEIf+KAv2VFipxMUoULXXS3qyLX3/q/4u8Y32fyM5wd+FXpv89cRCiveVsZp8io2W178R6lfKng==", "cpu": [ "arm64" ], @@ -10712,9 +10720,9 @@ ] }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.3.11.tgz", - "integrity": "sha512-/hJpc4VJsbxDEreXt5Ka9HJ3TBEHgIa9y/i+H9MmWOeapCdH1Edhx58Heuv9OaX7kK8Y8q0cSicv0dJCghiTjA==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-21.6.8.tgz", + "integrity": "sha512-UZJyrZ6utU8g1W7E31iHDhWj1SjMidmDNyrVP4xK6IUrotx6qGrwfwWqzqvphhc1cA7w4Zz9N/rCCPQkdHzjLw==", "cpu": [ "arm64" ], @@ -10725,9 +10733,9 @@ ] }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.3.11.tgz", - "integrity": "sha512-pTBHuloqTxpTHa/fdKjHkFFsfW16mEcTp37HDtoQpjPfcd9nO8CYO8OClaewr9khNqCnSbCLfSoIg/alnb7BWw==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-21.6.8.tgz", + "integrity": "sha512-HXINTg4P0/Yj76vsvqBAb7MNlLpkH1pl9JF2blXScOFXWzNoStd7b6xyrpCROdmi0Hk8y3UEwc8OIyLFIfixJg==", "cpu": [ "x64" ], @@ -10738,9 +10746,9 @@ ] }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.3.11.tgz", - "integrity": "sha512-OhFjURB68rd6xld8t8fiNpopF2E7v+8/jfbpsku9c0gdV2UhzoxCeZwooe7qhQjCcjVO8JNOs4dAf7qs1VtpMw==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-21.6.8.tgz", + "integrity": "sha512-2YbXLhuSlElCtQTR1Ib94O3T4fX9uzSIkUMYGL3n04agG0HemXoxJa91TWwwOUMbEZffkhcPsJBOh2S5l47s9Q==", "cpu": [ "x64" ], @@ -10751,9 +10759,9 @@ ] }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.3.11.tgz", - "integrity": "sha512-pGE2Td13oEj7aeogwCL+2fjmpabQVSduKfGOTlt4YoMlM0w0bXYSWqwiGBMKbMA50qkhnVapwwkuWF38PgCIxg==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-21.6.8.tgz", + "integrity": "sha512-/VSGtqa1oGHo5n24R39ZuGxMrGyf7pxFuCtL5hAzBHdTxFg/VZomPGd7BeV5VN5SbIw+fie+nTGkC5q3TOPGXw==", "cpu": [ "arm64" ], @@ -10764,9 +10772,9 @@ ] }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.3.11.tgz", - "integrity": "sha512-KJqLL/Zyx96hs+7pKbo/fsU7ZTFSLeZLnYQu05o6fvJJ5I1+p85t212/7vkbKKWJncyMospQdzLr3zLG3A/u8A==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-21.6.8.tgz", + "integrity": "sha512-xeBL7PcDqHH/Zw4d2A2qP7eLImzzcMO3hiKs5G42Wi92ACejAdUqpIduTL4RpArsXIHm5VEbE4KvHNii1mMU1A==", "cpu": [ "x64" ], @@ -10777,14 +10785,14 @@ ] }, "node_modules/@nx/webpack": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-21.3.11.tgz", - "integrity": "sha512-GAqA9yHLro4zDf2z27uWseUSLiZZh2IZ3Eh5Kb9l/LA4ujT3whkpNoIo/K2LxzmmOG8k2SkJ7wBntCPk2O1e8g==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-21.6.8.tgz", + "integrity": "sha512-lVJJRqtNPbdSrbEws3/pty077kwlY3m7XDQZTzStCf9ZGE5tZR8LDe49DKlUZ6dLIO7RmHjW8laehXsvi9ZtHw==", "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", - "@nx/devkit": "21.3.11", - "@nx/js": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/js": "21.6.8", "@phenomnomnominal/tsquery": "~5.0.1", "ajv": "^8.12.0", "autoprefixer": "^10.4.9", @@ -10812,9 +10820,9 @@ "style-loader": "^3.3.0", "terser-webpack-plugin": "^5.3.3", "ts-loader": "^9.3.1", - "tsconfig-paths-webpack-plugin": "4.0.0", + "tsconfig-paths-webpack-plugin": "4.2.0", "tslib": "^2.3.0", - "webpack": "~5.99.0", + "webpack": "^5.101.3", "webpack-dev-server": "^5.2.1", "webpack-node-externals": "^3.0.0", "webpack-subresource-integrity": "^5.1.0" @@ -10842,6 +10850,39 @@ "concat-map": "0.0.1" } }, + "node_modules/@nx/webpack/node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/@nx/webpack/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -10953,6 +10994,28 @@ } } }, + "node_modules/@nx/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/@nx/webpack/node_modules/fork-ts-checker-webpack-plugin": { "version": "7.2.13", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", @@ -11073,6 +11136,12 @@ "node": ">= 4" } }, + "node_modules/@nx/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11110,6 +11179,27 @@ "node": ">=8.9.0" } }, + "node_modules/@nx/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nx/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@nx/webpack/node_modules/mini-css-extract-plugin": { "version": "2.4.7", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.4.7.tgz", @@ -11225,15 +11315,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nx/webpack/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@nx/webpack/node_modules/style-loader": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", @@ -11250,34 +11331,67 @@ "webpack": "^5.0.0" } }, - "node_modules/@nx/webpack/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "node_modules/@nx/webpack/node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "license": "MIT", "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@nx/webpack/node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.0.tgz", - "integrity": "sha512-fw/7265mIWukrSHd0i+wSwx64kYUSAKPfxRDksjKIYTxSAp9W9/xcZVBF4Kl0eqQd5eBpAQ/oQrc5RyM/0c1GQ==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.7.0", - "tsconfig-paths": "^4.0.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { "node": ">=10.13.0" } }, + "node_modules/@nx/webpack/node_modules/webpack": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", + "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/@nx/webpack/node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -11288,17 +11402,18 @@ } }, "node_modules/@nx/workspace": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.3.11.tgz", - "integrity": "sha512-DD2iu9Ip/faNQ5MXZk+UbbBxGofYKjzHsXKRvMNQ/OAVzP/u9z2CPXEmRKlRAEQoy1lInmyopwfEUWwK1v4x0g==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-21.6.8.tgz", + "integrity": "sha512-X2DgtqxFJwwte4GxancLZ2GFMc+9waRaLPL6goR/HAtIvKriqZAVMkywNMk1mnhrpo0M7UXmfbM51kf+nsS+bg==", "license": "MIT", "dependencies": { - "@nx/devkit": "21.3.11", + "@nx/devkit": "21.6.8", "@zkochan/js-yaml": "0.0.7", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "21.3.11", + "nx": "21.6.8", "picomatch": "4.0.2", + "semver": "^7.6.3", "tslib": "^2.3.0", "yargs-parser": "21.1.1" } @@ -16133,6 +16248,18 @@ "acorn-walk": "^8.0.2" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -17236,9 +17363,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -17258,7 +17385,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -17322,6 +17449,15 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -18288,9 +18424,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001724", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", - "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", "funding": [ { "type": "opencollective", @@ -21081,9 +21217,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.172", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.172.tgz", - "integrity": "sha512-fnKW9dGgmBfsebbYognQSv0CGGLFH1a5iV9EDYTBwmAQn+whbzHbLFlC+3XbHc8xaNtpO0etm8LOcRXs1qMRkQ==", + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", "license": "ISC" }, "node_modules/electron-updater": { @@ -26163,15 +26299,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-diff/node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - } - }, "node_modules/jest-diff/node_modules/@jest/schemas": { "version": "30.0.5", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", @@ -28829,12 +28956,16 @@ "optional": true }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -31644,9 +31775,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, "node_modules/nopt": { @@ -32168,9 +32299,9 @@ "license": "MIT" }, "node_modules/nx": { - "version": "21.3.11", - "resolved": "https://registry.npmjs.org/nx/-/nx-21.3.11.tgz", - "integrity": "sha512-nj2snZ3mHZnbHcoB3NUdxbch9L1sQKV1XccLs1B79fmI/N5oOgWgctm/bWoZH2UH5b4A8ZLAMTsC6YnSJGbcaw==", + "version": "21.6.8", + "resolved": "https://registry.npmjs.org/nx/-/nx-21.6.8.tgz", + "integrity": "sha512-NilGEk1Cngs3Se9JW+f9cDeN6RBvmABhpEtgMvOK8RAAZszq6B380oCzKcQljhnrbQ6+v6j/Vb7hBPTCvXb0Ng==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -32178,7 +32309,7 @@ "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", - "axios": "^1.8.3", + "axios": "^1.12.0", "chalk": "^4.1.0", "cli-cursor": "3.1.0", "cli-spinners": "2.6.1", @@ -32215,16 +32346,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "21.3.11", - "@nx/nx-darwin-x64": "21.3.11", - "@nx/nx-freebsd-x64": "21.3.11", - "@nx/nx-linux-arm-gnueabihf": "21.3.11", - "@nx/nx-linux-arm64-gnu": "21.3.11", - "@nx/nx-linux-arm64-musl": "21.3.11", - "@nx/nx-linux-x64-gnu": "21.3.11", - "@nx/nx-linux-x64-musl": "21.3.11", - "@nx/nx-win32-arm64-msvc": "21.3.11", - "@nx/nx-win32-x64-msvc": "21.3.11" + "@nx/nx-darwin-arm64": "21.6.8", + "@nx/nx-darwin-x64": "21.6.8", + "@nx/nx-freebsd-x64": "21.6.8", + "@nx/nx-linux-arm-gnueabihf": "21.6.8", + "@nx/nx-linux-arm64-gnu": "21.6.8", + "@nx/nx-linux-arm64-musl": "21.6.8", + "@nx/nx-linux-x64-gnu": "21.6.8", + "@nx/nx-linux-x64-musl": "21.6.8", + "@nx/nx-win32-arm64-msvc": "21.6.8", + "@nx/nx-win32-x64-msvc": "21.6.8" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -36861,9 +36992,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -38640,12 +38771,16 @@ } }, "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { @@ -39477,7 +39612,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -39493,7 +39627,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -39503,7 +39636,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -40533,9 +40665,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index 5d23d6b9938..73ba1c05c91 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "json5": "2.2.3", "lint-staged": "16.0.0", "mini-css-extract-plugin": "2.9.2", - "nx": "21.3.11", + "nx": "21.6.8", "postcss": "8.5.3", "postcss-loader": "8.1.1", "prettier": "3.6.2", @@ -169,11 +169,11 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "14.9.0", - "@nx/devkit": "21.3.11", - "@nx/eslint": "21.3.11", - "@nx/jest": "21.3.11", - "@nx/js": "21.3.11", - "@nx/webpack": "21.3.11", + "@nx/devkit": "21.6.8", + "@nx/eslint": "21.6.8", + "@nx/jest": "21.6.8", + "@nx/js": "21.6.8", + "@nx/webpack": "21.6.8", "big-integer": "1.6.52", "braintree-web-drop-in": "1.44.0", "buffer": "6.0.3", From 490ef1dab011601737b5043afe311785941fe3cc Mon Sep 17 00:00:00 2001 From: Andreas Coroiu <acoroiu@bitwarden.com> Date: Fri, 21 Nov 2025 16:29:39 +0100 Subject: [PATCH 209/249] chore: ignore commercial sdk (#17585) --- .github/renovate.json5 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6b34998b99b..737ef5f7081 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -435,5 +435,11 @@ description: "Higher versions of lowdb do not need separate types", }, ], - ignoreDeps: ["@types/koa-bodyparser", "bootstrap", "node-ipc", "@bitwarden/sdk-internal"], + ignoreDeps: [ + "@types/koa-bodyparser", + "bootstrap", + "node-ipc", + "@bitwarden/sdk-internal", + "@bitwarden/commercial-sdk-internal", + ], } From daf7b7d2cef5d0342703f2d0d535163d5860ef54 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Fri, 21 Nov 2025 10:35:34 -0500 Subject: [PATCH 210/249] fix(two-factor) [PM-21204]: Users without premium cannot disable premium 2FA (#17134) * refactor(two-factor-service) [PM-21204]: Stub API methods in TwoFactorService (domain). * refactor(two-factor-service) [PM-21204]: Build out stubs and add documentation. * refactor(two-factor-service) [PM-21204]: Update TwoFactorApiService call sites to use TwoFactorService. * refactor(two-fatcor) [PM-21204]: Remove deprecated and unused formPromise methods. * refactor(two-factor) [PM-21204]: Move 2FA-supporting services into common/auth/two-factor feature namespace. * refactor(two-factor) [PM-21204]: Update imports for service/init containers. * feat(two-factor) [PM-21204]: Add a disabling flow for Premium 2FA when enabled on a non-Premium account. * fix(two-factor-service) [PM-21204]: Fix type-safety of module constants. * fix(multiple) [PM-21204]: Prettier. * fix(user-verification-dialog) [PM-21204]: Remove bodyText configuration for this use. * fix(user-verification-dialog) [PM-21204]: Improve the error message displayed to the user. --- .../src/popup/services/init.service.ts | 2 +- apps/cli/src/auth/commands/login.command.ts | 3 +- .../service-container/service-container.ts | 11 +- apps/desktop/src/app/services/init.service.ts | 4 +- .../settings/two-factor-setup.component.ts | 15 +- .../account/change-email.component.spec.ts | 10 +- .../account/change-email.component.ts | 6 +- ...account-verify-devices-dialog.component.ts | 6 +- ...wo-factor-setup-authenticator.component.ts | 10 +- .../two-factor-setup-duo.component.ts | 10 +- .../two-factor-setup-email.component.ts | 10 +- .../two-factor-setup-method-base.component.ts | 60 +-- .../two-factor-setup-webauthn.component.ts | 13 +- .../two-factor-setup-yubikey.component.ts | 11 +- .../two-factor-setup.component.html | 29 +- .../two-factor/two-factor-setup.component.ts | 59 ++- .../two-factor/two-factor-verify.component.ts | 18 +- apps/web/src/app/core/init.service.ts | 4 +- apps/web/src/locales/en/messages.json | 3 + .../src/services/jslib-services.module.ts | 22 +- .../two-factor-auth-email.component.ts | 6 +- .../two-factor-auth-webauthn.component.ts | 2 +- .../two-factor-auth.component.spec.ts | 2 +- .../two-factor-auth.component.ts | 2 +- .../two-factor-auth.guard.spec.ts | 2 +- .../two-factor-auth/two-factor-auth.guard.ts | 2 +- .../two-factor-options.component.ts | 5 +- .../user-verification-dialog.component.ts | 4 +- .../auth-request-login.strategy.spec.ts | 2 +- .../login-strategies/login.strategy.spec.ts | 2 +- .../common/login-strategies/login.strategy.ts | 2 +- .../password-login.strategy.spec.ts | 2 +- .../sso-login.strategy.spec.ts | 2 +- .../user-api-login.strategy.spec.ts | 2 +- .../webauthn-login.strategy.spec.ts | 2 +- .../login-strategy.service.spec.ts | 2 +- .../login-strategy.service.ts | 2 +- .../auth/abstractions/two-factor.service.ts | 60 --- .../src/auth/services/two-factor.service.ts | 212 -------- .../src/auth/two-factor/abstractions/index.ts | 2 + .../two-factor-api.service.ts | 0 .../abstractions/two-factor.service.ts | 497 ++++++++++++++++++ libs/common/src/auth/two-factor/index.ts | 4 +- .../default-two-factor-api.service.spec.ts} | 0 .../default-two-factor-api.service.ts | 2 +- .../services/default-two-factor.service.ts | 279 ++++++++++ .../src/auth/two-factor/services/index.ts | 2 + 47 files changed, 966 insertions(+), 441 deletions(-) delete mode 100644 libs/common/src/auth/abstractions/two-factor.service.ts delete mode 100644 libs/common/src/auth/services/two-factor.service.ts create mode 100644 libs/common/src/auth/two-factor/abstractions/index.ts rename libs/common/src/auth/two-factor/{ => abstractions}/two-factor-api.service.ts (100%) create mode 100644 libs/common/src/auth/two-factor/abstractions/two-factor.service.ts rename libs/common/src/auth/two-factor/{two-factor-api.service.spec.ts => services/default-two-factor-api.service.spec.ts} (100%) rename libs/common/src/auth/two-factor/{ => services}/default-two-factor-api.service.ts (99%) create mode 100644 libs/common/src/auth/two-factor/services/default-two-factor.service.ts create mode 100644 libs/common/src/auth/two-factor/services/index.ts diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index 1de1731013d..91b6f0ff105 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -2,7 +2,7 @@ import { DOCUMENT } from "@angular/common"; import { inject, Inject, Injectable } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index aa43b353f9c..d0ab062d0b3 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -20,7 +20,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -28,7 +27,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; import { ClientType } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index ebfb76eab2f..365205a1329 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -49,10 +49,14 @@ import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/defau import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; -import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { + DefaultTwoFactorService, + TwoFactorService, + TwoFactorApiService, + DefaultTwoFactorApiService, +} from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -627,10 +631,11 @@ export class ServiceContainer { this.stateProvider, ); - this.twoFactorService = new TwoFactorService( + this.twoFactorService = new DefaultTwoFactorService( this.i18nService, this.platformUtilsService, this.globalStateProvider, + this.twoFactorApiService, ); const sdkClientFactory = flagEnabled("sdk") diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index ae633bd4a69..73c4d38d3b2 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -39,7 +39,7 @@ export class InitService { private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, - private twoFactorService: TwoFactorServiceAbstraction, + private twoFactorService: TwoFactorService, private notificationsService: ServerNotificationsService, private platformUtilsService: PlatformUtilsServiceAbstraction, private stateService: StateServiceAbstraction, diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 46e39a112bf..95081af3a53 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -11,16 +11,17 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { DialogRef, DialogService } from "@bitwarden/components"; +import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; import { TwoFactorSetupDuoComponent } from "../../../auth/settings/two-factor/two-factor-setup-duo.component"; import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor/two-factor-setup.component"; @@ -37,7 +38,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme tabbedHeader = false; constructor( dialogService: DialogService, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, messagingService: MessagingService, policyService: PolicyService, private route: ActivatedRoute, @@ -46,16 +47,20 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme protected accountService: AccountService, configService: ConfigService, i18nService: I18nService, + protected userVerificationService: UserVerificationService, + protected toastService: ToastService, ) { super( dialogService, - twoFactorApiService, + twoFactorService, messagingService, policyService, billingAccountProfileStateService, accountService, configService, i18nService, + userVerificationService, + toastService, ); } @@ -118,7 +123,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme } protected getTwoFactorProviders() { - return this.twoFactorApiService.getTwoFactorOrganizationProviders(this.organizationId); + return this.twoFactorService.getTwoFactorOrganizationProviders(this.organizationId); } protected filterProvider(type: TwoFactorProviderType): boolean { diff --git a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts index 934de0f6453..56f2bfe2112 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.spec.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.spec.ts @@ -7,7 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -23,14 +23,14 @@ describe("ChangeEmailComponent", () => { let fixture: ComponentFixture<ChangeEmailComponent>; let apiService: MockProxy<ApiService>; - let twoFactorApiService: MockProxy<TwoFactorApiService>; + let twoFactorService: MockProxy<TwoFactorService>; let accountService: FakeAccountService; let keyService: MockProxy<KeyService>; let kdfConfigService: MockProxy<KdfConfigService>; beforeEach(async () => { apiService = mock<ApiService>(); - twoFactorApiService = mock<TwoFactorApiService>(); + twoFactorService = mock<TwoFactorService>(); keyService = mock<KeyService>(); kdfConfigService = mock<KdfConfigService>(); accountService = mockAccountServiceWith("UserId" as UserId); @@ -40,7 +40,7 @@ describe("ChangeEmailComponent", () => { providers: [ { provide: AccountService, useValue: accountService }, { provide: ApiService, useValue: apiService }, - { provide: TwoFactorApiService, useValue: twoFactorApiService }, + { provide: TwoFactorService, useValue: twoFactorService }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: KeyService, useValue: keyService }, { provide: MessagingService, useValue: mock<MessagingService>() }, @@ -61,7 +61,7 @@ describe("ChangeEmailComponent", () => { describe("ngOnInit", () => { beforeEach(() => { - twoFactorApiService.getTwoFactorProviders.mockResolvedValue({ + twoFactorService.getEnabledTwoFactorProviders.mockResolvedValue({ data: [{ type: TwoFactorProviderType.Email, enabled: true } as TwoFactorProviderResponse], } as ListResponse<TwoFactorProviderResponse>); }); diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index ee29e0c8a9c..3daf2240fb2 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -8,7 +8,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request"; import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -40,7 +40,7 @@ export class ChangeEmailComponent implements OnInit { constructor( private accountService: AccountService, private apiService: ApiService, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, private i18nService: I18nService, private keyService: KeyService, private messagingService: MessagingService, @@ -52,7 +52,7 @@ export class ChangeEmailComponent implements OnInit { async ngOnInit() { this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); - const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders(); this.showTwoFactorEmailWarning = twoFactorProviders.data.some( (p) => p.type === TwoFactorProviderType.Email && p.enabled, ); diff --git a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts index 01be46c45b3..97f50df24c8 100644 --- a/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/set-account-verify-devices-dialog.component.ts @@ -9,7 +9,7 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { SetVerifyDevicesRequest } from "@bitwarden/common/auth/models/request/set-verify-devices.request"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -66,7 +66,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy private userVerificationService: UserVerificationService, private dialogRef: DialogRef, private toastService: ToastService, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, ) { this.accountService.accountVerifyNewDeviceLogin$ .pipe(takeUntil(this.destroy$)) @@ -76,7 +76,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy } async ngOnInit() { - const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders(); + const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders(); this.has2faConfigured = twoFactorProviders.data.length > 0; } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts index 20c3c742db6..d93a5947445 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.ts @@ -12,7 +12,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request"; import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -96,7 +96,7 @@ export class TwoFactorSetupAuthenticatorComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorAuthenticatorResponse>, private dialogRef: DialogRef, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, userVerificationService: UserVerificationService, private formBuilder: FormBuilder, @@ -108,7 +108,7 @@ export class TwoFactorSetupAuthenticatorComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -158,7 +158,7 @@ export class TwoFactorSetupAuthenticatorComponent request.key = this.key; request.userVerificationToken = this.userVerificationToken; - const response = await this.twoFactorApiService.putTwoFactorAuthenticator(request); + const response = await this.twoFactorService.putTwoFactorAuthenticator(request); await this.processResponse(response); this.onUpdated.emit(true); } @@ -178,7 +178,7 @@ export class TwoFactorSetupAuthenticatorComponent request.type = this.type; request.key = this.key; request.userVerificationToken = this.userVerificationToken; - await this.twoFactorApiService.deleteTwoFactorAuthenticator(request); + await this.twoFactorService.deleteTwoFactorAuthenticator(request); this.enabled = false; this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts index 1a476f2206d..b4c8ece92a7 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts @@ -6,7 +6,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -67,7 +67,7 @@ export class TwoFactorSetupDuoComponent constructor( @Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -78,7 +78,7 @@ export class TwoFactorSetupDuoComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -143,12 +143,12 @@ export class TwoFactorSetupDuoComponent let response: TwoFactorDuoResponse; if (this.organizationId != null) { - response = await this.twoFactorApiService.putTwoFactorOrganizationDuo( + response = await this.twoFactorService.putTwoFactorOrganizationDuo( this.organizationId, request, ); } else { - response = await this.twoFactorApiService.putTwoFactorDuo(request); + response = await this.twoFactorService.putTwoFactorDuo(request); } this.processResponse(response); diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts index 4219fb0b687..1402d6b8969 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts @@ -9,7 +9,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -70,7 +70,7 @@ export class TwoFactorSetupEmailComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorEmailResponse>, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -82,7 +82,7 @@ export class TwoFactorSetupEmailComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -135,7 +135,7 @@ export class TwoFactorSetupEmailComponent sendEmail = async () => { const request = await this.buildRequestModel(TwoFactorEmailRequest); request.email = this.email; - this.emailPromise = this.twoFactorApiService.postTwoFactorEmailSetup(request); + this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request); await this.emailPromise; this.sentEmail = this.email; }; @@ -145,7 +145,7 @@ export class TwoFactorSetupEmailComponent request.email = this.email; request.token = this.token; - const response = await this.twoFactorApiService.putTwoFactorEmail(request); + const response = await this.twoFactorService.putTwoFactorEmail(request); await this.processResponse(response); this.onUpdated.emit(true); } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index ad8d401d3fc..5494353449d 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -5,7 +5,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -32,7 +32,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected componentName = ""; constructor( - protected twoFactorApiService: TwoFactorApiService, + protected twoFactorService: TwoFactorService, protected i18nService: I18nService, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, @@ -47,58 +47,6 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.authed = true; } - /** @deprecated used for formPromise flows.*/ - protected async enable(enableFunction: () => Promise<void>) { - try { - await enableFunction(); - this.onUpdated.emit(true); - } catch (e) { - this.logService.error(e); - } - } - - /** - * @deprecated used for formPromise flows. - * TODO: Remove this method when formPromises are removed from all flows. - * */ - protected async disable(promise: Promise<unknown>) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "disable" }, - content: { key: "twoStepDisableDesc" }, - type: "warning", - }); - - if (!confirmed) { - return; - } - - try { - const request = await this.buildRequestModel(TwoFactorProviderRequest); - if (this.type === undefined) { - throw new Error("Two-factor provider type is required"); - } - request.type = this.type; - if (this.organizationId != null) { - promise = this.twoFactorApiService.putTwoFactorOrganizationDisable( - this.organizationId, - request, - ); - } else { - promise = this.twoFactorApiService.putTwoFactorDisable(request); - } - await promise; - this.enabled = false; - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("twoStepDisabled"), - }); - this.onUpdated.emit(false); - } catch (e) { - this.logService.error(e); - } - } - protected async disableMethod() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "disable" }, @@ -116,9 +64,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { } request.type = this.type; if (this.organizationId != null) { - await this.twoFactorApiService.putTwoFactorOrganizationDisable(this.organizationId, request); + await this.twoFactorService.putTwoFactorOrganizationDisable(this.organizationId, request); } else { - await this.twoFactorApiService.putTwoFactorDisable(request); + await this.twoFactorService.putTwoFactorDisable(request); } this.enabled = false; this.toastService.showToast({ diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts index acf83ab278e..11ba5955902 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.ts @@ -12,7 +12,7 @@ import { ChallengeResponse, TwoFactorWebAuthnResponse, } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -72,7 +72,6 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom webAuthnListening: boolean = false; webAuthnResponse: PublicKeyCredential | null = null; challengePromise: Promise<ChallengeResponse> | undefined; - formPromise: Promise<TwoFactorWebAuthnResponse> | undefined; override componentName = "app-two-factor-webauthn"; @@ -81,7 +80,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom constructor( @Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorWebAuthnResponse>, private dialogRef: DialogRef, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, private ngZone: NgZone, @@ -91,7 +90,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -129,7 +128,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom request.id = this.keyIdAvailable; request.name = this.formGroup.value.name || ""; - const response = await this.twoFactorApiService.putTwoFactorWebAuthn(request); + const response = await this.twoFactorService.putTwoFactorWebAuthn(request); this.processResponse(response); this.toastService.showToast({ title: this.i18nService.t("success"), @@ -165,7 +164,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest); request.id = key.id; try { - key.removePromise = this.twoFactorApiService.deleteTwoFactorWebAuthn(request); + key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request); const response = await key.removePromise; key.removePromise = null; await this.processResponse(response); @@ -179,7 +178,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom return; } const request = await this.buildRequestModel(SecretVerificationRequest); - this.challengePromise = this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); + this.challengePromise = this.twoFactorService.getTwoFactorWebAuthnChallenge(request); const challenge = await this.challengePromise; this.readDevice(challenge); }; diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts index 09fb1ad308f..a58c659796d 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-yubikey.component.ts @@ -13,7 +13,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -74,9 +74,6 @@ export class TwoFactorSetupYubiKeyComponent keys: Key[] = []; anyKeyHasNfc = false; - formPromise: Promise<TwoFactorYubiKeyResponse> | undefined; - disablePromise: Promise<unknown> | undefined; - override componentName = "app-two-factor-yubikey"; formGroup: | FormGroup<{ @@ -95,7 +92,7 @@ export class TwoFactorSetupYubiKeyComponent constructor( @Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorYubiKeyResponse>, - twoFactorApiService: TwoFactorApiService, + twoFactorService: TwoFactorService, i18nService: I18nService, platformUtilsService: PlatformUtilsService, logService: LogService, @@ -105,7 +102,7 @@ export class TwoFactorSetupYubiKeyComponent protected toastService: ToastService, ) { super( - twoFactorApiService, + twoFactorService, i18nService, platformUtilsService, logService, @@ -178,7 +175,7 @@ export class TwoFactorSetupYubiKeyComponent request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : ""; request.nfc = this.formGroup.value.anyKeyHasNfc ?? false; - this.processResponse(await this.twoFactorApiService.putTwoFactorYubiKey(request)); + this.processResponse(await this.twoFactorService.putTwoFactorYubiKey(request)); this.refreshFormArrayData(); this.toastService.showToast({ title: this.i18nService.t("success"), diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html index ee2d4dd7b63..69a0dbf4145 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.html @@ -71,15 +71,26 @@ <div class="tw-mt-2 tw-text-wrap">{{ p.description }}</div> </div> <bit-item-action slot="end"> - <button - type="button" - bitButton - buttonType="secondary" - [disabled]="!(canAccessPremium$ | async) && p.premium" - (click)="manage(p.type)" - > - {{ "manage" | i18n }} - </button> + @if (p.premium && p.enabled && !(canAccessPremium$ | async)) { + <button + type="button" + bitButton + buttonType="danger" + (click)="disablePremium2faTypeForNonPremiumUser(p.type)" + > + {{ "disable" | i18n }} + </button> + } @else { + <button + type="button" + bitButton + buttonType="secondary" + [disabled]="!(canAccessPremium$ | async) && p.premium" + (click)="manage(p.type)" + > + {{ "manage" | i18n }} + </button> + } </bit-item-action> </bit-item> </bit-item-group> diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts index 024455cc1bf..a85bf65ab74 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup.component.ts @@ -12,26 +12,28 @@ import { } from "rxjs"; import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; +import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request"; import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response"; import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService, TwoFactorProviders } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { DialogRef, DialogService, ItemModule } from "@bitwarden/components"; +import { DialogRef, DialogService, ItemModule, ToastService } from "@bitwarden/components"; import { HeaderModule } from "../../../layouts/header/header.module"; import { SharedModule } from "../../../shared/shared.module"; @@ -59,7 +61,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { recoveryCodeWarningMessage: string; showPolicyWarning = false; loading = true; - formPromise: Promise<any>; tabbedHeader = true; @@ -69,13 +70,15 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { constructor( protected dialogService: DialogService, - protected twoFactorApiService: TwoFactorApiService, + protected twoFactorService: TwoFactorService, protected messagingService: MessagingService, protected policyService: PolicyService, billingAccountProfileStateService: BillingAccountProfileStateService, protected accountService: AccountService, protected configService: ConfigService, protected i18nService: I18nService, + protected userVerificationService: UserVerificationService, + protected toastService: ToastService, ) { this.canAccessPremium$ = this.accountService.activeAccount$.pipe( switchMap((account) => @@ -150,6 +153,50 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { return await lastValueFrom(twoFactorVerifyDialogRef.closed); } + /** + * For users who enabled a premium-only 2fa provider, + * they should still be allowed to disable that provider + * (without otherwise modifying) if they no longer have + * premium access [PM-21204] + * @param type the 2FA Provider Type + */ + async disablePremium2faTypeForNonPremiumUser(type: TwoFactorProviderType) { + // Use UserVerificationDialogComponent instead of TwoFactorVerifyComponent + // because the latter makes GET API calls that require premium for YubiKey/Duo. + // The disable endpoint only requires user verification, not provider configuration. + const result = await UserVerificationDialogComponent.open(this.dialogService, { + title: "twoStepLogin", + verificationType: { + type: "custom", + verificationFn: async (secret) => { + const request = await this.userVerificationService.buildRequest<TwoFactorProviderRequest>( + secret, + TwoFactorProviderRequest, + ); + request.type = type; + + await this.twoFactorService.putTwoFactorDisable(request); + return true; + }, + }, + }); + + if (result.userAction === "cancel") { + return; + } + + if (!result.verificationSuccess) { + return; + } + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("twoStepDisabled"), + }); + this.updateStatus(false, type); + } + async manage(type: TwoFactorProviderType) { // clear any existing subscriptions before creating a new one this.twoFactorSetupSubscription?.unsubscribe(); @@ -264,7 +311,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { } protected getTwoFactorProviders() { - return this.twoFactorApiService.getTwoFactorProviders(); + return this.twoFactorService.getEnabledTwoFactorProviders(); } protected filterProvider(type: TwoFactorProviderType): boolean { diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts index 075d3bdf562..04c84ca0197 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-verify.component.ts @@ -5,7 +5,7 @@ import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { TwoFactorResponse } from "@bitwarden/common/auth/types/two-factor-response"; import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; @@ -54,7 +54,7 @@ export class TwoFactorVerifyComponent { constructor( @Inject(DIALOG_DATA) protected data: TwoFactorVerifyDialogData, private dialogRef: DialogRef, - private twoFactorApiService: TwoFactorApiService, + private twoFactorService: TwoFactorService, private i18nService: I18nService, private userVerificationService: UserVerificationService, ) { @@ -110,22 +110,22 @@ export class TwoFactorVerifyComponent { private apiCall(request: SecretVerificationRequest): Promise<TwoFactorResponse> { switch (this.type) { case -1 as TwoFactorProviderType: - return this.twoFactorApiService.getTwoFactorRecover(request); + return this.twoFactorService.getTwoFactorRecover(request); case TwoFactorProviderType.Duo: case TwoFactorProviderType.OrganizationDuo: if (this.organizationId != null) { - return this.twoFactorApiService.getTwoFactorOrganizationDuo(this.organizationId, request); + return this.twoFactorService.getTwoFactorOrganizationDuo(this.organizationId, request); } else { - return this.twoFactorApiService.getTwoFactorDuo(request); + return this.twoFactorService.getTwoFactorDuo(request); } case TwoFactorProviderType.Email: - return this.twoFactorApiService.getTwoFactorEmail(request); + return this.twoFactorService.getTwoFactorEmail(request); case TwoFactorProviderType.WebAuthn: - return this.twoFactorApiService.getTwoFactorWebAuthn(request); + return this.twoFactorService.getTwoFactorWebAuthn(request); case TwoFactorProviderType.Authenticator: - return this.twoFactorApiService.getTwoFactorAuthenticator(request); + return this.twoFactorService.getTwoFactorAuthenticator(request); case TwoFactorProviderType.Yubikey: - return this.twoFactorApiService.getTwoFactorYubiKey(request); + return this.twoFactorService.getTwoFactorYubiKey(request); default: throw new Error(`Unknown two-factor type: ${this.type}`); } diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 6b03913ef7a..71e3578a7c6 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -31,7 +31,7 @@ export class InitService { private vaultTimeoutService: DefaultVaultTimeoutService, private i18nService: I18nServiceAbstraction, private eventUploadService: EventUploadServiceAbstraction, - private twoFactorService: TwoFactorServiceAbstraction, + private twoFactorService: TwoFactorService, private keyService: KeyServiceAbstraction, private themingService: AbstractThemingService, private encryptService: EncryptService, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 5cf1bea6fd8..dccb6d28af5 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -12178,5 +12178,8 @@ }, "confirmNoSelectedCriticalApplicationsDesc": { "message": "Are you sure you want to continue?" + }, + "userVerificationFailed": { + "message": "User verification failed." } } diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index b1215654cfd..ff5caff540c 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -102,7 +102,6 @@ import { MasterPasswordApiService as MasterPasswordApiServiceAbstraction } from import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { UserVerificationApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification-api.service.abstraction"; import { UserVerificationService as UserVerificationServiceAbstraction } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { WebAuthnLoginApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-api.service.abstraction"; @@ -125,13 +124,17 @@ import { OrganizationInviteService } from "@bitwarden/common/auth/services/organ import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service"; import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service"; import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service"; import { WebAuthnLoginApiService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-api.service"; import { WebAuthnLoginPrfKeyService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login-prf-key.service"; import { WebAuthnLoginService } from "@bitwarden/common/auth/services/webauthn-login/webauthn-login.service"; -import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { + TwoFactorApiService, + DefaultTwoFactorApiService, + TwoFactorService, + DefaultTwoFactorService, +} from "@bitwarden/common/auth/two-factor"; import { AutofillSettingsService, AutofillSettingsServiceAbstraction, @@ -527,7 +530,7 @@ const safeProviders: SafeProvider[] = [ KeyConnectorServiceAbstraction, EnvironmentService, StateServiceAbstraction, - TwoFactorServiceAbstraction, + TwoFactorService, I18nServiceAbstraction, EncryptService, PasswordStrengthServiceAbstraction, @@ -1165,9 +1168,14 @@ const safeProviders: SafeProvider[] = [ deps: [StateProvider], }), safeProvider({ - provide: TwoFactorServiceAbstraction, - useClass: TwoFactorService, - deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction, GlobalStateProvider], + provide: TwoFactorService, + useClass: DefaultTwoFactorService, + deps: [ + I18nServiceAbstraction, + PlatformUtilsServiceAbstraction, + GlobalStateProvider, + TwoFactorApiService, + ], }), safeProvider({ provide: FormValidationErrorsServiceAbstraction, diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 000d391b62c..8976236f48c 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -4,10 +4,9 @@ import { ReactiveFormsModule, FormsModule, FormControl } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; -import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -68,7 +67,6 @@ export class TwoFactorAuthEmailComponent implements OnInit { protected loginStrategyService: LoginStrategyServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected logService: LogService, - protected twoFactorApiService: TwoFactorApiService, protected appIdService: AppIdService, private toastService: ToastService, private cacheService: TwoFactorAuthEmailComponentCacheService, @@ -137,7 +135,7 @@ export class TwoFactorAuthEmailComponent implements OnInit { request.deviceIdentifier = await this.appIdService.getAppId(); request.authRequestAccessCode = (await this.loginStrategyService.getAccessCode()) ?? ""; request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? ""; - this.emailPromise = this.twoFactorApiService.postTwoFactorEmail(request); + this.emailPromise = this.twoFactorService.postTwoFactorEmail(request); await this.emailPromise; this.emailSent = true; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 71a91ec20e7..54d6f7c5f77 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -6,8 +6,8 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 5d36fd384ca..af9fb03e01e 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -18,12 +18,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 52f20b04601..8e10539823d 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -32,12 +32,12 @@ import { import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts index 116da73173f..37fbf6bf794 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.spec.ts @@ -4,8 +4,8 @@ import { provideRouter, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { LoginStrategyServiceAbstraction } from "../../common"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts index 2aec0bae441..a7ed4010ef5 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.guard.ts @@ -8,7 +8,7 @@ import { } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { LoginStrategyServiceAbstraction } from "../../common"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index d0ad9be6103..d8b2ab2508b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -9,11 +9,8 @@ import { TwoFactorAuthWebAuthnIcon, TwoFactorAuthYubicoIcon, } from "@bitwarden/assets/svg"; -import { - TwoFactorProviderDetails, - TwoFactorService, -} from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +import { TwoFactorProviderDetails, TwoFactorService } from "@bitwarden/common/auth/two-factor"; // 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 { diff --git a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts index 09d428d4ba7..1304df2a763 100644 --- a/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts +++ b/libs/auth/src/angular/user-verification/user-verification-dialog.component.ts @@ -277,13 +277,13 @@ export class UserVerificationDialogComponent { }); } } - } catch (e) { + } catch { // Catch handles OTP and MP verification scenarios as those throw errors on verification failure instead of returning false like PIN and biometrics. this.invalidSecret = true; this.toastService.showToast({ variant: "error", title: this.i18nService.t("error"), - message: e.message, + message: this.i18nService.t("userVerificationFailed"), }); return; } diff --git a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts index 0b19fecdc4e..b536ae0dc4b 100644 --- a/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/auth-request-login.strategy.spec.ts @@ -3,8 +3,8 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index a23f8034238..e9eed27d5a1 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -5,7 +5,6 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -16,6 +15,7 @@ import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/id import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 7ad5cd24353..35f13246593 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -3,7 +3,6 @@ import { BehaviorSubject, filter, firstValueFrom, timeout, Observable } from "rx import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -16,6 +15,7 @@ import { WebAuthnLoginTokenRequest } from "@bitwarden/common/auth/models/request import { IdentityDeviceVerificationResponse } from "@bitwarden/common/auth/models/response/identity-device-verification.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; diff --git a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts index 3f709e7472b..26ae1270f39 100644 --- a/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/password-login.strategy.spec.ts @@ -5,12 +5,12 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index bce05b35e62..03de5f36c2d 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -3,12 +3,12 @@ import { BehaviorSubject, of } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { EncryptedString } from "@bitwarden/common/key-management/crypto/models/enc-string"; diff --git a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts index a6446401f70..9bf282dee11 100644 --- a/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/user-api-login.strategy.spec.ts @@ -3,7 +3,7 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; diff --git a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts index 25ae8a31ef6..53bc1c57905 100644 --- a/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/webauthn-login.strategy.spec.ts @@ -3,11 +3,11 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IUserDecryptionOptionsServerResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service"; diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index f97158477f1..831692c160f 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -4,13 +4,13 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 084fafc4aea..5720fce254e 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -13,10 +13,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; +import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; diff --git a/libs/common/src/auth/abstractions/two-factor.service.ts b/libs/common/src/auth/abstractions/two-factor.service.ts deleted file mode 100644 index 528e52bf5da..00000000000 --- a/libs/common/src/auth/abstractions/two-factor.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; -import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; - -export interface TwoFactorProviderDetails { - type: TwoFactorProviderType; - name: string; - description: string; - priority: number; - sort: number; - premium: boolean; -} -export abstract class TwoFactorService { - /** - * Initializes the client-side's TwoFactorProviders const with translations. - */ - abstract init(): void; - - /** - * Gets a list of two-factor providers from state that are supported on the current client. - * E.g., WebAuthn and Duo are not available on all clients. - * @returns A list of supported two-factor providers or an empty list if none are stored in state. - */ - abstract getSupportedProviders(win: Window): Promise<TwoFactorProviderDetails[]>; - - /** - * Gets the previously selected two-factor provider or the default two factor provider based on priority. - * @param webAuthnSupported - Whether or not WebAuthn is supported by the client. Prevents WebAuthn from being the default provider if false. - */ - abstract getDefaultProvider(webAuthnSupported: boolean): Promise<TwoFactorProviderType>; - - /** - * Sets the selected two-factor provider in state. - * @param type - The type of two-factor provider to set as the selected provider. - */ - abstract setSelectedProvider(type: TwoFactorProviderType): Promise<void>; - - /** - * Clears the selected two-factor provider from state. - */ - abstract clearSelectedProvider(): Promise<void>; - - /** - * Sets the list of available two-factor providers in state. - * @param response - the response from Identity for when 2FA is required. Includes the list of available 2FA providers. - */ - abstract setProviders(response: IdentityTwoFactorResponse): Promise<void>; - - /** - * Clears the list of available two-factor providers from state. - */ - abstract clearProviders(): Promise<void>; - - /** - * Gets the list of two-factor providers from state. - * Note: no filtering is done here, so this will return all providers, including potentially - * unsupported ones for the current client. - * @returns A list of two-factor providers or null if none are stored in state. - */ - abstract getProviders(): Promise<Map<TwoFactorProviderType, { [key: string]: string }> | null>; -} diff --git a/libs/common/src/auth/services/two-factor.service.ts b/libs/common/src/auth/services/two-factor.service.ts deleted file mode 100644 index 83e113268a2..00000000000 --- a/libs/common/src/auth/services/two-factor.service.ts +++ /dev/null @@ -1,212 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; - -import { I18nService } from "../../platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service"; -import { Utils } from "../../platform/misc/utils"; -import { GlobalStateProvider, KeyDefinition, TWO_FACTOR_MEMORY } from "../../platform/state"; -import { - TwoFactorProviderDetails, - TwoFactorService as TwoFactorServiceAbstraction, -} from "../abstractions/two-factor.service"; -import { TwoFactorProviderType } from "../enums/two-factor-provider-type"; -import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response"; - -export const TwoFactorProviders: Partial<Record<TwoFactorProviderType, TwoFactorProviderDetails>> = - { - [TwoFactorProviderType.Authenticator]: { - type: TwoFactorProviderType.Authenticator, - name: null as string, - description: null as string, - priority: 1, - sort: 2, - premium: false, - }, - [TwoFactorProviderType.Yubikey]: { - type: TwoFactorProviderType.Yubikey, - name: null as string, - description: null as string, - priority: 3, - sort: 4, - premium: true, - }, - [TwoFactorProviderType.Duo]: { - type: TwoFactorProviderType.Duo, - name: "Duo", - description: null as string, - priority: 2, - sort: 5, - premium: true, - }, - [TwoFactorProviderType.OrganizationDuo]: { - type: TwoFactorProviderType.OrganizationDuo, - name: "Duo (Organization)", - description: null as string, - priority: 10, - sort: 6, - premium: false, - }, - [TwoFactorProviderType.Email]: { - type: TwoFactorProviderType.Email, - name: null as string, - description: null as string, - priority: 0, - sort: 1, - premium: false, - }, - [TwoFactorProviderType.WebAuthn]: { - type: TwoFactorProviderType.WebAuthn, - name: null as string, - description: null as string, - priority: 4, - sort: 3, - premium: false, - }, - }; - -// Memory storage as only required during authentication process -export const PROVIDERS = KeyDefinition.record<Record<string, string>, TwoFactorProviderType>( - TWO_FACTOR_MEMORY, - "providers", - { - deserializer: (obj) => obj, - }, -); - -// Memory storage as only required during authentication process -export const SELECTED_PROVIDER = new KeyDefinition<TwoFactorProviderType>( - TWO_FACTOR_MEMORY, - "selected", - { - deserializer: (obj) => obj, - }, -); - -export class TwoFactorService implements TwoFactorServiceAbstraction { - private providersState = this.globalStateProvider.get(PROVIDERS); - private selectedState = this.globalStateProvider.get(SELECTED_PROVIDER); - readonly providers$ = this.providersState.state$.pipe( - map((providers) => Utils.recordToMap(providers)), - ); - readonly selected$ = this.selectedState.state$; - - constructor( - private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, - private globalStateProvider: GlobalStateProvider, - ) {} - - init() { - TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle"); - TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDescV2"); - - TwoFactorProviders[TwoFactorProviderType.Authenticator].name = - this.i18nService.t("authenticatorAppTitle"); - TwoFactorProviders[TwoFactorProviderType.Authenticator].description = - this.i18nService.t("authenticatorAppDescV2"); - - TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDescV2"); - - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = - "Duo (" + this.i18nService.t("organization") + ")"; - TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = - this.i18nService.t("duoOrganizationDesc"); - - TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle"); - TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = - this.i18nService.t("webAuthnDesc"); - - TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitleV2"); - TwoFactorProviders[TwoFactorProviderType.Yubikey].description = - this.i18nService.t("yubiKeyDesc"); - } - - async getSupportedProviders(win: Window): Promise<TwoFactorProviderDetails[]> { - const data = await firstValueFrom(this.providers$); - const providers: any[] = []; - if (data == null) { - return providers; - } - - if ( - data.has(TwoFactorProviderType.OrganizationDuo) && - this.platformUtilsService.supportsDuo() - ) { - providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); - } - - if (data.has(TwoFactorProviderType.Authenticator)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); - } - - if (data.has(TwoFactorProviderType.Yubikey)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); - } - - if (data.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); - } - - if ( - data.has(TwoFactorProviderType.WebAuthn) && - this.platformUtilsService.supportsWebAuthn(win) - ) { - providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); - } - - if (data.has(TwoFactorProviderType.Email)) { - providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); - } - - return providers; - } - - async getDefaultProvider(webAuthnSupported: boolean): Promise<TwoFactorProviderType> { - const data = await firstValueFrom(this.providers$); - const selected = await firstValueFrom(this.selected$); - if (data == null) { - return null; - } - - if (selected != null && data.has(selected)) { - return selected; - } - - let providerType: TwoFactorProviderType = null; - let providerPriority = -1; - data.forEach((_value, type) => { - const provider = (TwoFactorProviders as any)[type]; - if (provider != null && provider.priority > providerPriority) { - if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { - return; - } - - providerType = type; - providerPriority = provider.priority; - } - }); - - return providerType; - } - - async setSelectedProvider(type: TwoFactorProviderType): Promise<void> { - await this.selectedState.update(() => type); - } - - async clearSelectedProvider(): Promise<void> { - await this.selectedState.update(() => null); - } - - async setProviders(response: IdentityTwoFactorResponse): Promise<void> { - await this.providersState.update(() => response.twoFactorProviders2); - } - - async clearProviders(): Promise<void> { - await this.providersState.update(() => null); - } - - getProviders(): Promise<Map<TwoFactorProviderType, { [key: string]: string }> | null> { - return firstValueFrom(this.providers$); - } -} diff --git a/libs/common/src/auth/two-factor/abstractions/index.ts b/libs/common/src/auth/two-factor/abstractions/index.ts new file mode 100644 index 00000000000..00789048ebe --- /dev/null +++ b/libs/common/src/auth/two-factor/abstractions/index.ts @@ -0,0 +1,2 @@ +export * from "./two-factor-api.service"; +export * from "./two-factor.service"; diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts similarity index 100% rename from libs/common/src/auth/two-factor/two-factor-api.service.ts rename to libs/common/src/auth/two-factor/abstractions/two-factor-api.service.ts diff --git a/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts new file mode 100644 index 00000000000..4cba40716e1 --- /dev/null +++ b/libs/common/src/auth/two-factor/abstractions/two-factor.service.ts @@ -0,0 +1,497 @@ +import { ListResponse } from "../../../models/response/list.response"; +import { KeyDefinition, TWO_FACTOR_MEMORY } from "../../../platform/state"; +import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; +import { DisableTwoFactorAuthenticatorRequest } from "../../models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "../../models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "../../models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "../../models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "../../models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "../../models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "../../models/request/update-two-factor-yubikey-otp.request"; +import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; +import { + ChallengeResponse, + TwoFactorWebAuthnResponse, +} from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; + +/** + * Metadata and display information for a two-factor authentication provider. + * Used by UI components to render provider selection and configuration screens. + */ +export interface TwoFactorProviderDetails { + /** The unique identifier for this provider type. */ + type: TwoFactorProviderType; + + /** + * Display name for the provider, localized via {@link TwoFactorService.init}. + * Examples: "Authenticator App", "Email", "YubiKey". + */ + name: string | null; + + /** + * User-facing description explaining what this provider is and how it works. + * Localized via {@link TwoFactorService.init}. + */ + description: string | null; + + /** + * Selection priority during login when multiple providers are available. + * Higher values are preferred. Used to determine the default provider. + * Range: 0 (lowest) to 10 (highest). + */ + priority: number; + + /** + * Display order in provider lists within settings UI. + * Lower values appear first (1 = first position). + */ + sort: number; + + /** + * Whether this provider requires an active premium subscription. + * Premium providers: Duo (personal), YubiKey. + * Organization providers (e.g., OrganizationDuo) do not require personal premium. + */ + premium: boolean; +} + +/** + * Registry of all supported two-factor authentication providers with their metadata. + * Strings (name, description) are initialized as null and populated with localized + * translations when {@link TwoFactorService.init} is called during application startup. + * + * @remarks + * This constant is mutated during initialization. Components should not access it before + * the service's init() method has been called. + * + * @example + * ```typescript + * // During app init + * twoFactorService.init(); + * + * // In components + * const authenticator = TwoFactorProviders[TwoFactorProviderType.Authenticator]; + * console.log(authenticator.name); // "Authenticator App" (localized) + * ``` + */ +export const TwoFactorProviders: Partial<Record<TwoFactorProviderType, TwoFactorProviderDetails>> = + { + [TwoFactorProviderType.Authenticator]: { + type: TwoFactorProviderType.Authenticator, + name: null, + description: null, + priority: 1, + sort: 2, + premium: false, + }, + [TwoFactorProviderType.Yubikey]: { + type: TwoFactorProviderType.Yubikey, + name: null, + description: null, + priority: 3, + sort: 4, + premium: true, + }, + [TwoFactorProviderType.Duo]: { + type: TwoFactorProviderType.Duo, + name: "Duo", + description: null, + priority: 2, + sort: 5, + premium: true, + }, + [TwoFactorProviderType.OrganizationDuo]: { + type: TwoFactorProviderType.OrganizationDuo, + name: "Duo (Organization)", + description: null, + priority: 10, + sort: 6, + premium: false, + }, + [TwoFactorProviderType.Email]: { + type: TwoFactorProviderType.Email, + name: null, + description: null, + priority: 0, + sort: 1, + premium: false, + }, + [TwoFactorProviderType.WebAuthn]: { + type: TwoFactorProviderType.WebAuthn, + name: null, + description: null, + priority: 4, + sort: 3, + premium: false, + }, + }; + +// Memory storage as only required during authentication process +export const PROVIDERS = KeyDefinition.record<Record<string, string>, TwoFactorProviderType>( + TWO_FACTOR_MEMORY, + "providers", + { + deserializer: (obj) => obj, + }, +); + +// Memory storage as only required during authentication process +export const SELECTED_PROVIDER = new KeyDefinition<TwoFactorProviderType>( + TWO_FACTOR_MEMORY, + "selected", + { + deserializer: (obj) => obj, + }, +); + +export abstract class TwoFactorService { + /** + * Initializes the client-side's TwoFactorProviders const with translations. + */ + abstract init(): void; + + /** + * Gets a list of two-factor providers from state that are supported on the current client. + * E.g., WebAuthn and Duo are not available on all clients. + * @returns A list of supported two-factor providers or an empty list if none are stored in state. + */ + abstract getSupportedProviders(win: Window): Promise<TwoFactorProviderDetails[]>; + + /** + * Gets the previously selected two-factor provider or the default two factor provider based on priority. + * @param webAuthnSupported - Whether or not WebAuthn is supported by the client. Prevents WebAuthn from being the default provider if false. + */ + abstract getDefaultProvider(webAuthnSupported: boolean): Promise<TwoFactorProviderType>; + + /** + * Sets the selected two-factor provider in state. + * @param type - The type of two-factor provider to set as the selected provider. + */ + abstract setSelectedProvider(type: TwoFactorProviderType): Promise<void>; + + /** + * Clears the selected two-factor provider from state. + */ + abstract clearSelectedProvider(): Promise<void>; + + /** + * Sets the list of available two-factor providers in state. + * @param response - the response from Identity for when 2FA is required. Includes the list of available 2FA providers. + */ + abstract setProviders(response: IdentityTwoFactorResponse): Promise<void>; + + /** + * Clears the list of available two-factor providers from state. + */ + abstract clearProviders(): Promise<void>; + + /** + * Gets the list of two-factor providers from state. + * Note: no filtering is done here, so this will return all providers, including potentially + * unsupported ones for the current client. + * @returns A list of two-factor providers or null if none are stored in state. + */ + abstract getProviders(): Promise<Map<TwoFactorProviderType, { [key: string]: string }> | null>; + + /** + * Gets the enabled two-factor providers for the current user from the API. + * Used for settings management. + * @returns A promise that resolves to a list response containing enabled two-factor provider configurations. + */ + abstract getEnabledTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>>; + + /** + * Gets the enabled two-factor providers for an organization from the API. + * Requires organization administrator permissions. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @returns A promise that resolves to a list response containing enabled two-factor provider configurations. + */ + abstract getTwoFactorOrganizationProviders( + organizationId: string, + ): Promise<ListResponse<TwoFactorProviderResponse>>; + + /** + * Gets the authenticator (TOTP) two-factor configuration for the current user from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the authenticator configuration including the secret key. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise<TwoFactorAuthenticatorResponse>; + + /** + * Gets the email two-factor configuration for the current user from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the email two-factor configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorEmail(request: SecretVerificationRequest): Promise<TwoFactorEmailResponse>; + + /** + * Gets the Duo two-factor configuration for the current user from the API. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorDuo(request: SecretVerificationRequest): Promise<TwoFactorDuoResponse>; + + /** + * Gets the Duo two-factor configuration for an organization from the API. + * Requires user verification and organization policy management permissions. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the organization Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise<TwoFactorDuoResponse>; + + /** + * Gets the YubiKey OTP two-factor configuration for the current user from the API. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the YubiKey configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorYubiKey( + request: SecretVerificationRequest, + ): Promise<TwoFactorYubiKeyResponse>; + + /** + * Gets the WebAuthn (FIDO2) two-factor configuration for the current user from the API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to authentication. + * @returns A promise that resolves to the WebAuthn configuration including registered credentials. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorWebAuthn( + request: SecretVerificationRequest, + ): Promise<TwoFactorWebAuthnResponse>; + + /** + * Gets a WebAuthn challenge for registering a new WebAuthn credential from the API. + * This must be called before putTwoFactorWebAuthn to obtain the cryptographic challenge + * required for credential creation. The challenge is used by the browser's WebAuthn API. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link SecretVerificationRequest} to prove authentication. + * @returns A promise that resolves to the credential creation options containing the challenge. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorWebAuthnChallenge( + request: SecretVerificationRequest, + ): Promise<ChallengeResponse>; + + /** + * Gets the recovery code configuration for the current user from the API. + * The recovery code should be stored securely by the user. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param verification The verification information to prove authentication. + * @returns A promise that resolves to the recovery code configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract getTwoFactorRecover( + request: SecretVerificationRequest, + ): Promise<TwoFactorRecoverResponse>; + + /** + * Enables or updates the authenticator (TOTP) two-factor provider. + * Validates the provided token against the shared secret before enabling. + * The token must be generated by an authenticator app using the secret key. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorAuthenticatorRequest} to prove authentication. + * @returns A promise that resolves to the updated authenticator configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise<TwoFactorAuthenticatorResponse>; + + /** + * Disables the authenticator (TOTP) two-factor provider for the current user. + * Requires user verification token to confirm the operation. + * Used for settings management. + * + * @param request The {@link DisableTwoFactorAuthenticatorRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise<TwoFactorProviderResponse>; + + /** + * Enables or updates the email two-factor provider for the current user. + * Validates the email verification token sent via postTwoFactorEmailSetup before enabling. + * The token must match the code sent to the specified email address. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves to the updated email two-factor configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise<TwoFactorEmailResponse>; + + /** + * Enables or updates the Duo two-factor provider for the current user. + * Validates the Duo configuration (client ID, client secret, and host) before enabling. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @returns A promise that resolves to the updated Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise<TwoFactorDuoResponse>; + + /** + * Enables or updates the Duo two-factor provider for an organization. + * Validates the Duo configuration (client ID, client secret, and host) before enabling. + * Requires user verification and organization policy management permissions. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link UpdateTwoFactorDuoRequest} to prove authentication. + * @returns A promise that resolves to the updated organization Duo configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise<TwoFactorDuoResponse>; + + /** + * Enables or updates the YubiKey OTP two-factor provider for the current user. + * Validates each provided YubiKey by testing an OTP from the device. + * Supports up to 5 YubiKey devices. Empty key slots are allowed. + * Requires user verification and an active premium subscription. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorYubikeyOtpRequest} to prove authentication. + * @returns A promise that resolves to the updated YubiKey configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise<TwoFactorYubiKeyResponse>; + + /** + * Registers a new WebAuthn (FIDO2) credential for two-factor authentication for the current user. + * Must be called after getTwoFactorWebAuthnChallenge to complete the registration flow. + * The device response contains the signed challenge from the authenticator device. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorWebAuthnRequest} to prove authentication. + * @returns A promise that resolves to the updated WebAuthn configuration with the new credential. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnRequest, + ): Promise<TwoFactorWebAuthnResponse>; + + /** + * Removes a specific WebAuthn (FIDO2) credential from the user's account. + * The credential will no longer be usable for two-factor authentication. + * Other registered WebAuthn credentials remain active. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link UpdateTwoFactorWebAuthnDeleteRequest} to prove authentication. + * @returns A promise that resolves to the updated WebAuthn configuration. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract deleteTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnDeleteRequest, + ): Promise<TwoFactorWebAuthnResponse>; + + /** + * Disables a specific two-factor provider for the current user. + * The provider will no longer be required or usable for authentication. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorDisable( + request: TwoFactorProviderRequest, + ): Promise<TwoFactorProviderResponse>; + + /** + * Disables a specific two-factor provider for an organization. + * The provider will no longer be available for organization members. + * Requires user verification and organization policy management permissions. + * Used for settings management. + * + * @param organizationId The ID of the organization. + * @param request The {@link TwoFactorProviderRequest} to prove authentication. + * @returns A promise that resolves to the updated provider status. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract putTwoFactorOrganizationDisable( + organizationId: string, + request: TwoFactorProviderRequest, + ): Promise<TwoFactorProviderResponse>; + + /** + * Initiates email two-factor setup by sending a verification code to the specified email address. + * This is the first step in enabling email two-factor authentication. + * The verification code must be provided to putTwoFactorEmail to complete setup. + * Only used during initial configuration, not during login flows. + * Requires user verification via master password or OTP. + * Used for settings management. + * + * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves when the verification email has been sent. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise<any>; + + /** + * Sends a two-factor authentication code via email during the login flow. + * Supports multiple authentication contexts including standard login, SSO, and passwordless flows. + * This is used to deliver codes during authentication, not during initial setup. + * May be called without authentication for login scenarios. + * Used during authentication flows. + * + * @param request The {@link TwoFactorEmailRequest} to prove authentication. + * @returns A promise that resolves when the authentication email has been sent. + * @remarks Use {@link UserVerificationService.buildRequest} to create the request object. + */ + abstract postTwoFactorEmail(request: TwoFactorEmailRequest): Promise<any>; +} diff --git a/libs/common/src/auth/two-factor/index.ts b/libs/common/src/auth/two-factor/index.ts index 85e072403b7..fd6edf0ac3c 100644 --- a/libs/common/src/auth/two-factor/index.ts +++ b/libs/common/src/auth/two-factor/index.ts @@ -1,2 +1,2 @@ -export { TwoFactorApiService } from "./two-factor-api.service"; -export { DefaultTwoFactorApiService } from "./default-two-factor-api.service"; +export * from "./abstractions"; +export * from "./services"; diff --git a/libs/common/src/auth/two-factor/two-factor-api.service.spec.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts similarity index 100% rename from libs/common/src/auth/two-factor/two-factor-api.service.spec.ts rename to libs/common/src/auth/two-factor/services/default-two-factor-api.service.spec.ts diff --git a/libs/common/src/auth/two-factor/default-two-factor-api.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts similarity index 99% rename from libs/common/src/auth/two-factor/default-two-factor-api.service.ts rename to libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts index 93f7b207922..b9778e460ea 100644 --- a/libs/common/src/auth/two-factor/default-two-factor-api.service.ts +++ b/libs/common/src/auth/two-factor/services/default-two-factor-api.service.ts @@ -22,7 +22,7 @@ import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { TwoFactorApiService } from "./two-factor-api.service"; +import { TwoFactorApiService } from "../abstractions/two-factor-api.service"; export class DefaultTwoFactorApiService implements TwoFactorApiService { constructor(private apiService: ApiService) {} diff --git a/libs/common/src/auth/two-factor/services/default-two-factor.service.ts b/libs/common/src/auth/two-factor/services/default-two-factor.service.ts new file mode 100644 index 00000000000..2acc4a7e939 --- /dev/null +++ b/libs/common/src/auth/two-factor/services/default-two-factor.service.ts @@ -0,0 +1,279 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore +import { firstValueFrom, map } from "rxjs"; + +import { TwoFactorApiService } from ".."; +import { ListResponse } from "../../../models/response/list.response"; +import { I18nService } from "../../../platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; +import { Utils } from "../../../platform/misc/utils"; +import { GlobalStateProvider } from "../../../platform/state"; +import { TwoFactorProviderType } from "../../enums/two-factor-provider-type"; +import { DisableTwoFactorAuthenticatorRequest } from "../../models/request/disable-two-factor-authenticator.request"; +import { SecretVerificationRequest } from "../../models/request/secret-verification.request"; +import { TwoFactorEmailRequest } from "../../models/request/two-factor-email.request"; +import { TwoFactorProviderRequest } from "../../models/request/two-factor-provider.request"; +import { UpdateTwoFactorAuthenticatorRequest } from "../../models/request/update-two-factor-authenticator.request"; +import { UpdateTwoFactorDuoRequest } from "../../models/request/update-two-factor-duo.request"; +import { UpdateTwoFactorEmailRequest } from "../../models/request/update-two-factor-email.request"; +import { UpdateTwoFactorWebAuthnDeleteRequest } from "../../models/request/update-two-factor-web-authn-delete.request"; +import { UpdateTwoFactorWebAuthnRequest } from "../../models/request/update-two-factor-web-authn.request"; +import { UpdateTwoFactorYubikeyOtpRequest } from "../../models/request/update-two-factor-yubikey-otp.request"; +import { IdentityTwoFactorResponse } from "../../models/response/identity-two-factor.response"; +import { TwoFactorAuthenticatorResponse } from "../../models/response/two-factor-authenticator.response"; +import { TwoFactorDuoResponse } from "../../models/response/two-factor-duo.response"; +import { TwoFactorEmailResponse } from "../../models/response/two-factor-email.response"; +import { TwoFactorProviderResponse } from "../../models/response/two-factor-provider.response"; +import { TwoFactorRecoverResponse } from "../../models/response/two-factor-recover.response"; +import { + TwoFactorWebAuthnResponse, + ChallengeResponse, +} from "../../models/response/two-factor-web-authn.response"; +import { TwoFactorYubiKeyResponse } from "../../models/response/two-factor-yubi-key.response"; +import { + PROVIDERS, + SELECTED_PROVIDER, + TwoFactorProviderDetails, + TwoFactorProviders, + TwoFactorService as TwoFactorServiceAbstraction, +} from "../abstractions/two-factor.service"; + +export class DefaultTwoFactorService implements TwoFactorServiceAbstraction { + private providersState = this.globalStateProvider.get(PROVIDERS); + private selectedState = this.globalStateProvider.get(SELECTED_PROVIDER); + readonly providers$ = this.providersState.state$.pipe( + map((providers) => Utils.recordToMap(providers)), + ); + readonly selected$ = this.selectedState.state$; + + constructor( + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, + private globalStateProvider: GlobalStateProvider, + private twoFactorApiService: TwoFactorApiService, + ) {} + + init() { + TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t("emailTitle"); + TwoFactorProviders[TwoFactorProviderType.Email].description = this.i18nService.t("emailDescV2"); + + TwoFactorProviders[TwoFactorProviderType.Authenticator].name = + this.i18nService.t("authenticatorAppTitle"); + TwoFactorProviders[TwoFactorProviderType.Authenticator].description = + this.i18nService.t("authenticatorAppDescV2"); + + TwoFactorProviders[TwoFactorProviderType.Duo].description = this.i18nService.t("duoDescV2"); + + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].name = + "Duo (" + this.i18nService.t("organization") + ")"; + TwoFactorProviders[TwoFactorProviderType.OrganizationDuo].description = + this.i18nService.t("duoOrganizationDesc"); + + TwoFactorProviders[TwoFactorProviderType.WebAuthn].name = this.i18nService.t("webAuthnTitle"); + TwoFactorProviders[TwoFactorProviderType.WebAuthn].description = + this.i18nService.t("webAuthnDesc"); + + TwoFactorProviders[TwoFactorProviderType.Yubikey].name = this.i18nService.t("yubiKeyTitleV2"); + TwoFactorProviders[TwoFactorProviderType.Yubikey].description = + this.i18nService.t("yubiKeyDesc"); + } + + async getSupportedProviders(win: Window): Promise<TwoFactorProviderDetails[]> { + const data = await firstValueFrom(this.providers$); + const providers: any[] = []; + if (data == null) { + return providers; + } + + if ( + data.has(TwoFactorProviderType.OrganizationDuo) && + this.platformUtilsService.supportsDuo() + ) { + providers.push(TwoFactorProviders[TwoFactorProviderType.OrganizationDuo]); + } + + if (data.has(TwoFactorProviderType.Authenticator)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Authenticator]); + } + + if (data.has(TwoFactorProviderType.Yubikey)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Yubikey]); + } + + if (data.has(TwoFactorProviderType.Duo) && this.platformUtilsService.supportsDuo()) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Duo]); + } + + if ( + data.has(TwoFactorProviderType.WebAuthn) && + this.platformUtilsService.supportsWebAuthn(win) + ) { + providers.push(TwoFactorProviders[TwoFactorProviderType.WebAuthn]); + } + + if (data.has(TwoFactorProviderType.Email)) { + providers.push(TwoFactorProviders[TwoFactorProviderType.Email]); + } + + return providers; + } + + async getDefaultProvider(webAuthnSupported: boolean): Promise<TwoFactorProviderType> { + const data = await firstValueFrom(this.providers$); + const selected = await firstValueFrom(this.selected$); + if (data == null) { + return null; + } + + if (selected != null && data.has(selected)) { + return selected; + } + + let providerType: TwoFactorProviderType = null; + let providerPriority = -1; + data.forEach((_value, type) => { + const provider = (TwoFactorProviders as any)[type]; + if (provider != null && provider.priority > providerPriority) { + if (type === TwoFactorProviderType.WebAuthn && !webAuthnSupported) { + return; + } + + providerType = type; + providerPriority = provider.priority; + } + }); + + return providerType; + } + + async setSelectedProvider(type: TwoFactorProviderType): Promise<void> { + await this.selectedState.update(() => type); + } + + async clearSelectedProvider(): Promise<void> { + await this.selectedState.update(() => null); + } + + async setProviders(response: IdentityTwoFactorResponse): Promise<void> { + await this.providersState.update(() => response.twoFactorProviders2); + } + + async clearProviders(): Promise<void> { + await this.providersState.update(() => null); + } + + getProviders(): Promise<Map<TwoFactorProviderType, { [key: string]: string }> | null> { + return firstValueFrom(this.providers$); + } + + getEnabledTwoFactorProviders(): Promise<ListResponse<TwoFactorProviderResponse>> { + return this.twoFactorApiService.getTwoFactorProviders(); + } + + getTwoFactorOrganizationProviders( + organizationId: string, + ): Promise<ListResponse<TwoFactorProviderResponse>> { + return this.twoFactorApiService.getTwoFactorOrganizationProviders(organizationId); + } + + getTwoFactorAuthenticator( + request: SecretVerificationRequest, + ): Promise<TwoFactorAuthenticatorResponse> { + return this.twoFactorApiService.getTwoFactorAuthenticator(request); + } + + getTwoFactorEmail(request: SecretVerificationRequest): Promise<TwoFactorEmailResponse> { + return this.twoFactorApiService.getTwoFactorEmail(request); + } + + getTwoFactorDuo(request: SecretVerificationRequest): Promise<TwoFactorDuoResponse> { + return this.twoFactorApiService.getTwoFactorDuo(request); + } + + getTwoFactorOrganizationDuo( + organizationId: string, + request: SecretVerificationRequest, + ): Promise<TwoFactorDuoResponse> { + return this.twoFactorApiService.getTwoFactorOrganizationDuo(organizationId, request); + } + + getTwoFactorYubiKey(request: SecretVerificationRequest): Promise<TwoFactorYubiKeyResponse> { + return this.twoFactorApiService.getTwoFactorYubiKey(request); + } + + getTwoFactorWebAuthn(request: SecretVerificationRequest): Promise<TwoFactorWebAuthnResponse> { + return this.twoFactorApiService.getTwoFactorWebAuthn(request); + } + + getTwoFactorWebAuthnChallenge(request: SecretVerificationRequest): Promise<ChallengeResponse> { + return this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request); + } + + getTwoFactorRecover(request: SecretVerificationRequest): Promise<TwoFactorRecoverResponse> { + return this.twoFactorApiService.getTwoFactorRecover(request); + } + + putTwoFactorAuthenticator( + request: UpdateTwoFactorAuthenticatorRequest, + ): Promise<TwoFactorAuthenticatorResponse> { + return this.twoFactorApiService.putTwoFactorAuthenticator(request); + } + + deleteTwoFactorAuthenticator( + request: DisableTwoFactorAuthenticatorRequest, + ): Promise<TwoFactorProviderResponse> { + return this.twoFactorApiService.deleteTwoFactorAuthenticator(request); + } + + putTwoFactorEmail(request: UpdateTwoFactorEmailRequest): Promise<TwoFactorEmailResponse> { + return this.twoFactorApiService.putTwoFactorEmail(request); + } + + putTwoFactorDuo(request: UpdateTwoFactorDuoRequest): Promise<TwoFactorDuoResponse> { + return this.twoFactorApiService.putTwoFactorDuo(request); + } + + putTwoFactorOrganizationDuo( + organizationId: string, + request: UpdateTwoFactorDuoRequest, + ): Promise<TwoFactorDuoResponse> { + return this.twoFactorApiService.putTwoFactorOrganizationDuo(organizationId, request); + } + + putTwoFactorYubiKey( + request: UpdateTwoFactorYubikeyOtpRequest, + ): Promise<TwoFactorYubiKeyResponse> { + return this.twoFactorApiService.putTwoFactorYubiKey(request); + } + + putTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnRequest, + ): Promise<TwoFactorWebAuthnResponse> { + return this.twoFactorApiService.putTwoFactorWebAuthn(request); + } + + deleteTwoFactorWebAuthn( + request: UpdateTwoFactorWebAuthnDeleteRequest, + ): Promise<TwoFactorWebAuthnResponse> { + return this.twoFactorApiService.deleteTwoFactorWebAuthn(request); + } + + putTwoFactorDisable(request: TwoFactorProviderRequest): Promise<TwoFactorProviderResponse> { + return this.twoFactorApiService.putTwoFactorDisable(request); + } + + putTwoFactorOrganizationDisable( + organizationId: string, + request: TwoFactorProviderRequest, + ): Promise<TwoFactorProviderResponse> { + return this.twoFactorApiService.putTwoFactorOrganizationDisable(organizationId, request); + } + + postTwoFactorEmailSetup(request: TwoFactorEmailRequest): Promise<any> { + return this.twoFactorApiService.postTwoFactorEmailSetup(request); + } + + postTwoFactorEmail(request: TwoFactorEmailRequest): Promise<any> { + return this.twoFactorApiService.postTwoFactorEmail(request); + } +} diff --git a/libs/common/src/auth/two-factor/services/index.ts b/libs/common/src/auth/two-factor/services/index.ts new file mode 100644 index 00000000000..283f7b34631 --- /dev/null +++ b/libs/common/src/auth/two-factor/services/index.ts @@ -0,0 +1,2 @@ +export * from "./default-two-factor-api.service"; +export * from "./default-two-factor.service"; From 23d566685e9fae07384a32a216114ea079532d2d Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:53:44 +0100 Subject: [PATCH 211/249] Add clap and async-trait as tool owned dependencies (#17579) --- .github/renovate.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 737ef5f7081..6e142edf8a7 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -363,6 +363,8 @@ "@types/jsdom", "@types/papaparse", "@types/zxcvbn", + "async-trait", + "clap", "jsdom", "jszip", "oidc-client-ts", From 279632d65f1e8dfe1af0a1d2fdce5c11bd5637c1 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Fri, 21 Nov 2025 12:10:03 -0500 Subject: [PATCH 212/249] [PM-28516] Inline menu is not working in main (#17524) * PM-28516 alidate iframe and stylesheet URLs against their own origins to handle cases where chrome assigns different extension ids in different contexts * switch to regex to match exisiting match pattern * updated regex to account for safari --- .../autofill/background/overlay.background.ts | 17 ++++++---- .../autofill-inline-menu-container.ts | 1 + .../autofill-inline-menu-container.spec.ts | 34 +++++++++++++++++++ .../autofill-inline-menu-container.ts | 23 ++++++++----- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 225cbbe66ca..0eb7d070de3 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -2949,17 +2949,21 @@ export class OverlayBackground implements OverlayBackgroundInterface { (await this.checkFocusedFieldHasValue(port.sender.tab)) && (await this.shouldShowSaveLoginInlineMenuList(port.sender.tab)); + const iframeUrl = chrome.runtime.getURL( + `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.html`, + ); + const styleSheetUrl = chrome.runtime.getURL( + `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.css`, + ); + const extensionOrigin = new URL(iframeUrl).origin; + this.postMessageToPort(port, { command: `initAutofillInlineMenu${isInlineMenuListPort ? "List" : "Button"}`, - iframeUrl: chrome.runtime.getURL( - `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.html`, - ), + iframeUrl, pageTitle: chrome.i18n.getMessage( isInlineMenuListPort ? "bitwardenVault" : "bitwardenOverlayButton", ), - styleSheetUrl: chrome.runtime.getURL( - `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.css`, - ), + styleSheetUrl, theme: await firstValueFrom(this.themeStateService.selectedTheme$), translations: this.getInlineMenuTranslations(), ciphers: isInlineMenuListPort ? await this.getInlineMenuCipherData() : null, @@ -2973,6 +2977,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { showSaveLoginMenu, showInlineMenuAccountCreation, authStatus, + extensionOrigin, }); this.updateInlineMenuPosition( port.sender, diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts index af60d1de77d..64fa8dde124 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts @@ -17,6 +17,7 @@ export type InitAutofillInlineMenuElementMessage = AutofillInlineMenuContainerMe translations: Record<string, string>; ciphers: InlineMenuCipherData[] | null; portName: string; + extensionOrigin?: string; }; export type AutofillInlineMenuContainerWindowMessage = AutofillInlineMenuContainerMessage & diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts index d7a61bec61f..e0a6e626b3c 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.spec.ts @@ -184,4 +184,38 @@ describe("AutofillInlineMenuContainer", () => { expect(port.postMessage).not.toHaveBeenCalled(); }); }); + + describe("isExtensionUrlWithOrigin", () => { + it("validates extension URLs with matching origin", () => { + const url = "chrome-extension://test-id/path/to/file.html"; + const origin = "chrome-extension://test-id"; + + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"](url, origin)).toBe(true); + }); + + it("rejects extension URLs with mismatched origin", () => { + const url = "chrome-extension://test-id/path/to/file.html"; + const origin = "chrome-extension://different-id"; + + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"](url, origin)).toBe(false); + }); + + it("validates extension URL against its own origin when no expectedOrigin provided", () => { + const url = "moz-extension://test-id/path/to/file.html"; + + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"](url)).toBe(true); + }); + + it("rejects non-extension protocols", () => { + const url = "https://example.com/path"; + const origin = "https://example.com"; + + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"](url, origin)).toBe(false); + }); + + it("rejects empty or invalid URLs", () => { + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"]("")).toBe(false); + expect(autofillInlineMenuContainer["isExtensionUrlWithOrigin"]("not-a-url")).toBe(false); + }); + }); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts index ad0b11f0bc6..aea6ef30b99 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts @@ -87,11 +87,13 @@ export class AutofillInlineMenuContainer { return; } - if (!this.isExtensionUrl(message.iframeUrl)) { + const expectedOrigin = message.extensionOrigin || this.extensionOrigin; + + if (!this.isExtensionUrlWithOrigin(message.iframeUrl, expectedOrigin)) { return; } - if (message.styleSheetUrl && !this.isExtensionUrl(message.styleSheetUrl)) { + if (message.styleSheetUrl && !this.isExtensionUrlWithOrigin(message.styleSheetUrl)) { return; } @@ -115,20 +117,25 @@ export class AutofillInlineMenuContainer { } /** - * validates that a URL is from the extension origin. - * prevents loading arbitrary URLs in the iframe. + * Validates that a URL uses an extension protocol and matches the expected extension origin. + * If no expectedOrigin is provided, validates against the URL's own origin. * * @param url - The URL to validate. */ - private isExtensionUrl(url: string): boolean { + private isExtensionUrlWithOrigin(url: string, expectedOrigin?: string): boolean { if (!url) { return false; } try { const urlObj = new URL(url); - return ( - urlObj.origin === this.extensionOrigin || urlObj.href.startsWith(this.extensionOrigin + "/") - ); + const isExtensionProtocol = /^[a-z]+(-[a-z]+)?-extension:$/i.test(urlObj.protocol); + + if (!isExtensionProtocol) { + return false; + } + + const originToValidate = expectedOrigin ?? urlObj.origin; + return urlObj.origin === originToValidate || urlObj.href.startsWith(originToValidate + "/"); } catch { return false; } From 129c21cfb87d7352a2f059154aa8908a9414baf3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:23:51 -0500 Subject: [PATCH 213/249] [deps] Vault: Update koa to v2.16.3 [SECURITY] (#17514) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 10 +++++----- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index fc38440b70f..a7a7bfd0c7b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -75,7 +75,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.2", + "koa": "2.16.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", diff --git a/package-lock.json b/package-lock.json index d6883a3405e..005eb38d80b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.2", + "koa": "2.16.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.0", @@ -213,7 +213,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.2", + "koa": "2.16.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -28074,9 +28074,9 @@ } }, "node_modules/koa": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.2.tgz", - "integrity": "sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.3.tgz", + "integrity": "sha512-zPPuIt+ku1iCpFBRwseMcPYQ1cJL8l60rSmKeOuGfOXyE6YnTBmf2aEFNL2HQGrD0cPcLO/t+v9RTgC+fwEh/g==", "license": "MIT", "dependencies": { "accepts": "^1.3.5", diff --git a/package.json b/package.json index 73ba1c05c91..7ca866b3c4d 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.2", + "koa": "2.16.3", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.0", From aa2d2637518aa049fd07272f0d830b970826e897 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:48:50 -0600 Subject: [PATCH 214/249] [PM-24505] Manually open extension error message (#17116) * update manual open message to be more generic to cover more scenarios * update error state when attempting to open the extension via button press --- ...browser-extension-prompt.component.spec.ts | 4 +-- .../browser-extension-prompt.component.ts | 5 ++-- .../manually-open-extension.component.html | 8 ++--- .../manually-open-extension.component.ts | 5 ++-- .../setup-extension.component.html | 30 ++++++++----------- .../setup-extension.component.spec.ts | 13 -------- .../setup-extension.component.ts | 24 +++++++-------- apps/web/src/locales/en/messages.json | 23 +++++++------- 8 files changed, 44 insertions(+), 68 deletions(-) diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts index a19606f6d9c..b31759a1fe1 100644 --- a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.spec.ts @@ -166,8 +166,8 @@ describe("BrowserExtensionPromptComponent", () => { it("shows manual open error message", () => { const manualText = fixture.debugElement.query(By.css("p")).nativeElement; - expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart1"); - expect(manualText.textContent.trim()).toContain("openExtensionManuallyPart2"); + expect(manualText.textContent.trim()).toContain("openExtensionFromToolbarPart1"); + expect(manualText.textContent.trim()).toContain("openExtensionFromToolbarPart2"); }); }); }); diff --git a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts index cb927d0848c..505a0df5032 100644 --- a/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts +++ b/apps/web/src/app/vault/components/browser-extension-prompt/browser-extension-prompt.component.ts @@ -1,5 +1,5 @@ import { CommonModule, DOCUMENT } from "@angular/common"; -import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; +import { Component, Inject, OnDestroy, OnInit, ChangeDetectionStrategy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; import { map, Observable, of, tap } from "rxjs"; @@ -14,12 +14,11 @@ import { } from "../../services/browser-extension-prompt.service"; import { ManuallyOpenExtensionComponent } from "../manually-open-extension/manually-open-extension.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({ selector: "vault-browser-extension-prompt", templateUrl: "./browser-extension-prompt.component.html", imports: [CommonModule, I18nPipe, ButtonComponent, IconModule, ManuallyOpenExtensionComponent], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class BrowserExtensionPromptComponent implements OnInit, OnDestroy { protected VaultMessages = VaultMessages; diff --git a/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.html b/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.html index 22c36e51177..d15cdaa712b 100644 --- a/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.html +++ b/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.html @@ -1,8 +1,8 @@ -<p bitTypography="body1" class="tw-mb-0 tw-text-xl"> - {{ "openExtensionManuallyPart1" | i18n }} +<p bitTypography="body1" class="tw-mb-0"> + {{ "openExtensionFromToolbarPart1" | i18n }} <bit-icon [icon]="BitwardenIcon" - class="[&>svg]:tw-align-baseline [&>svg]:-tw-mb-[2px]" + class="!tw-inline-block [&>svg]:tw-align-baseline [&>svg]:-tw-mb-[0.25rem]" ></bit-icon> - {{ "openExtensionManuallyPart2" | i18n }} + {{ "openExtensionFromToolbarPart2" | i18n }} </p> diff --git a/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.ts b/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.ts index 6105aeacf9c..435e847f6e9 100644 --- a/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.ts +++ b/apps/web/src/app/vault/components/manually-open-extension/manually-open-extension.component.ts @@ -1,12 +1,11 @@ -import { Component } from "@angular/core"; +import { Component, ChangeDetectionStrategy } from "@angular/core"; import { BitwardenIcon } from "@bitwarden/assets/svg"; import { IconModule } 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({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: "vault-manually-open-extension", templateUrl: "./manually-open-extension.component.html", imports: [I18nPipe, IconModule], diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html index 038c258d4b6..1976321b4ee 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.html @@ -29,10 +29,7 @@ </div> </section> -<section - *ngIf="state === SetupExtensionState.Success || state === SetupExtensionState.AlreadyInstalled" - class="tw-flex tw-flex-col tw-items-center" -> +<section *ngIf="showSuccessUI" class="tw-flex tw-flex-col tw-items-center"> <div class="tw-size-[90px]"> <bit-icon [icon]="PartyIcon"></bit-icon> </div> @@ -40,20 +37,15 @@ {{ (state === SetupExtensionState.Success ? "bitwardenExtensionInstalled" - : "openTheBitwardenExtension" + : "bitwardenExtensionIsInstalled" ) | i18n }} </h1> <div - class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8 tw-max-w-md tw-text-center" + class="tw-flex tw-flex-col tw-rounded-2xl tw-bg-background tw-border tw-border-solid tw-border-secondary-300 tw-p-8 tw-max-w-md md:tw-w-[28rem] tw-text-center" > <p> - {{ - (state === SetupExtensionState.Success - ? "openExtensionToAutofill" - : "bitwardenExtensionInstalledOpenExtension" - ) | i18n - }} + {{ "openExtensionToAutofill" | i18n }} </p> <button type="button" bitButton buttonType="primary" class="tw-mb-2" (click)="openExtension()"> {{ "openBitwardenExtension" | i18n }} @@ -61,8 +53,16 @@ <a bitButton buttonType="secondary" routerLink="/vault"> {{ "skipToWebApp" | i18n }} </a> + <div + *ngIf="state === SetupExtensionState.ManualOpen" + aria-live="polite" + class="tw-text-center tw-mt-4" + > + <vault-manually-open-extension></vault-manually-open-extension> + </div> </div> - <p class="tw-mt-10 tw-max-w-96 tw-text-center"> + + <p class="tw-mt-4 tw-max-w-96 tw-text-center"> {{ "gettingStartedWithBitwardenPart1" | i18n }} <a bitLink href="https://bitwarden.com/help/learning-center/"> {{ "gettingStartedWithBitwardenPart2" | i18n }} @@ -73,7 +73,3 @@ </a> </p> </section> - -<section *ngIf="state === SetupExtensionState.ManualOpen" aria-live="polite" class="tw-text-center"> - <vault-manually-open-extension></vault-manually-open-extension> -</section> diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts index fbf61f9a277..ef67e072116 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.spec.ts @@ -4,7 +4,6 @@ import { Router, RouterModule } from "@angular/router"; import { BehaviorSubject } from "rxjs"; import { SYSTEM_THEME_OBSERVABLE } from "@bitwarden/angular/services/injection-tokens"; -import { BrowserExtensionIcon } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DeviceType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -12,7 +11,6 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { StateProvider } from "@bitwarden/common/platform/state"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { AnonLayoutWrapperDataService } from "@bitwarden/components"; import { WebBrowserInteractionService } from "../../services/web-browser-interaction.service"; @@ -25,14 +23,12 @@ describe("SetupExtensionComponent", () => { const navigate = jest.fn().mockResolvedValue(true); const openExtension = jest.fn().mockResolvedValue(true); const update = jest.fn().mockResolvedValue(true); - const setAnonLayoutWrapperData = jest.fn(); const extensionInstalled$ = new BehaviorSubject<boolean | null>(null); beforeEach(async () => { navigate.mockClear(); openExtension.mockClear(); update.mockClear(); - setAnonLayoutWrapperData.mockClear(); window.matchMedia = jest.fn().mockReturnValue(false); await TestBed.configureTestingModule({ @@ -43,7 +39,6 @@ describe("SetupExtensionComponent", () => { { provide: PlatformUtilsService, useValue: { getDevice: () => DeviceType.UnknownBrowser } }, { provide: SYSTEM_THEME_OBSERVABLE, useValue: new BehaviorSubject("system") }, { provide: ThemeStateService, useValue: { selectedTheme$: new BehaviorSubject("system") } }, - { provide: AnonLayoutWrapperDataService, useValue: { setAnonLayoutWrapperData } }, { provide: AccountService, useValue: { activeAccount$: new BehaviorSubject({ account: { id: "account-id" } }) }, @@ -133,14 +128,6 @@ describe("SetupExtensionComponent", () => { tick(); expect(component["state"]).toBe(SetupExtensionState.ManualOpen); - expect(setAnonLayoutWrapperData).toHaveBeenCalledWith({ - pageTitle: { - key: "somethingWentWrong", - }, - pageIcon: BrowserExtensionIcon, - hideCardWrapper: false, - maxWidth: "md", - }); })); }); }); diff --git a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts index 974e73bc91e..809e404f5f1 100644 --- a/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts +++ b/apps/web/src/app/vault/components/setup-extension/setup-extension.component.ts @@ -5,7 +5,7 @@ import { Router, RouterModule } from "@angular/router"; import { firstValueFrom, pairwise, startWith } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { BrowserExtensionIcon, Party } from "@bitwarden/assets/svg"; +import { Party } from "@bitwarden/assets/svg"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -14,7 +14,6 @@ import { StateProvider } from "@bitwarden/common/platform/state"; import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values"; import { getWebStoreUrl } from "@bitwarden/common/vault/utils/get-web-store-url"; import { - AnonLayoutWrapperDataService, ButtonComponent, CenterPositionStrategy, DialogRef, @@ -68,7 +67,6 @@ export class SetupExtensionComponent implements OnInit, OnDestroy { private stateProvider = inject(StateProvider); private accountService = inject(AccountService); private document = inject(DOCUMENT); - private anonLayoutWrapperDataService = inject(AnonLayoutWrapperDataService); protected SetupExtensionState = SetupExtensionState; protected PartyIcon = Party; @@ -144,6 +142,16 @@ export class SetupExtensionComponent implements OnInit, OnDestroy { } } + get showSuccessUI(): boolean { + const successStates = [ + SetupExtensionState.Success, + SetupExtensionState.AlreadyInstalled, + SetupExtensionState.ManualOpen, + ] as string[]; + + return successStates.includes(this.state); + } + /** Opens the add extension later dialog */ addItLater() { this.dialogRef = this.dialogService.open<unknown, AddExtensionLaterDialogData>( @@ -161,16 +169,6 @@ export class SetupExtensionComponent implements OnInit, OnDestroy { async openExtension() { await this.webBrowserExtensionInteractionService.openExtension().catch(() => { this.state = SetupExtensionState.ManualOpen; - - // Update the anon layout data to show the proper error design - this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ - pageTitle: { - key: "somethingWentWrong", - }, - pageIcon: BrowserExtensionIcon, - hideCardWrapper: false, - maxWidth: "md", - }); }); } diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index dccb6d28af5..90468c61d5c 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -11351,14 +11351,6 @@ "openedExtensionViewAtRiskPasswords": { "message": "Successfully opened the Bitwarden browser extension. You can now review your at-risk passwords." }, - "openExtensionManuallyPart1": { - "message": "We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon", - "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" - }, - "openExtensionManuallyPart2": { - "message": "from the toolbar.", - "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'We had trouble opening the Bitwarden browser extension. Open the Bitwarden icon [Bitwarden Icon] from the toolbar.'" - }, "resellerRenewalWarningMsg": { "message": "Your subscription will renew soon. To ensure uninterrupted service, contact $RESELLER$ to confirm your renewal before $RENEWAL_DATE$.", "placeholders": { @@ -11679,11 +11671,8 @@ "bitwardenExtensionInstalled": { "message": "Bitwarden extension installed!" }, - "openTheBitwardenExtension": { - "message": "Open the Bitwarden extension" - }, - "bitwardenExtensionInstalledOpenExtension": { - "message": "The Bitwarden extension is installed! Open the extension to log in and start autofilling." + "bitwardenExtensionIsInstalled": { + "message": "Bitwarden extension is installed!" }, "openExtensionToAutofill": { "message": "Open the extension to log in and start autofilling." @@ -11699,6 +11688,14 @@ "message": "Learning Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" }, + "openExtensionFromToolbarPart1": { + "message": "If the extension didn't open, you may need to open Bitwarden from the icon ", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, + "openExtensionFromToolbarPart2": { + "message": " on the toolbar.", + "description": "This will be used as part of a larger sentence, broken up to include the Bitwarden icon. The full sentence will read 'If the extension didn't open, you may need to open Bitwarden from the icon [Bitwarden Icon] on the toolbar.'" + }, "gettingStartedWithBitwardenPart3": { "message": "Help Center", "description": "This will be displayed as part of a larger sentence. The whole sentence reads: 'For tips on getting started with Bitwarden visit the Learning Center and Help Center'" From 23ac477bbc120c331da5cb70bea86c6b8c2cc91a Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:01:41 -0500 Subject: [PATCH 215/249] chore(feature-flag): Removed pm-28325-remove-pm-22110-disable-alternate-login-methods flag --- .../organization-options.component.ts | 17 +----- .../auth/src/angular/login/login.component.ts | 11 +--- ...ault-login-success-handler.service.spec.ts | 58 +++++-------------- .../default-login-success-handler.service.ts | 21 +++---- libs/common/src/enums/feature-flag.enum.ts | 2 - 5 files changed, 27 insertions(+), 82 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 981e5703cb3..3b707f2d78c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -25,7 +25,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -181,13 +180,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { message: this.i18nService.t("unlinkedSso"), }); - const disableAlternateLoginMethodsFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM22110_DisableAlternateLoginMethods, - ); - - if (disableAlternateLoginMethodsFlagEnabled) { - await this.removeEmailFromSsoRequiredCacheIfPresent(); - } + await this.removeEmailFromSsoRequiredCacheIfPresent(); } catch (e) { this.logService.error(e); } @@ -214,13 +207,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { message: this.i18nService.t("leftOrganization"), }); - const disableAlternateLoginMethodsFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM22110_DisableAlternateLoginMethods, - ); - - if (disableAlternateLoginMethodsFlagEnabled) { - await this.removeEmailFromSsoRequiredCacheIfPresent(); - } + await this.removeEmailFromSsoRequiredCacheIfPresent(); } catch (e) { this.logService.error(e); } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 51379ed213e..0b011b5641f 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -205,14 +205,9 @@ export class LoginComponent implements OnInit, OnDestroy { await this.loadRememberedEmail(); } - const disableAlternateLoginMethodsFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM22110_DisableAlternateLoginMethods, - ); - if (disableAlternateLoginMethodsFlagEnabled) { - // This SSO required check should come after email has had a chance to be pre-filled (if it - // was found in query params or was the remembered email) - await this.determineIfSsoRequired(); - } + // This SSO required check should come after email has had a chance to be pre-filled (if it + // was found in query params or was the remembered email) + await this.determineIfSsoRequired(); } private async desktopOnInit(): Promise<void> { diff --git a/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.spec.ts b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.spec.ts index 86f7be8dfc7..975e065e21e 100644 --- a/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.spec.ts +++ b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.spec.ts @@ -1,7 +1,6 @@ import { MockProxy, mock } from "jest-mock-extended"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; @@ -58,62 +57,35 @@ describe("DefaultLoginSuccessHandlerService", () => { expect(loginEmailService.clearLoginEmail).toHaveBeenCalled(); }); - describe("when PM22110_DisableAlternateLoginMethods flag is disabled", () => { + it("should get SSO email", async () => { + await service.run(userId); + + expect(ssoLoginService.getSsoEmail).toHaveBeenCalled(); + }); + + describe("given SSO email is not found", () => { beforeEach(() => { - configService.getFeatureFlag.mockResolvedValue(false); + ssoLoginService.getSsoEmail.mockResolvedValue(null); }); - it("should not check SSO requirements", async () => { + it("should log error and return early", async () => { await service.run(userId); - expect(ssoLoginService.getSsoEmail).not.toHaveBeenCalled(); + expect(logService.error).toHaveBeenCalledWith("SSO login email not found."); expect(ssoLoginService.updateSsoRequiredCache).not.toHaveBeenCalled(); }); }); - describe("given PM22110_DisableAlternateLoginMethods flag is enabled", () => { + describe("given SSO email is found", () => { beforeEach(() => { - configService.getFeatureFlag.mockResolvedValue(true); + ssoLoginService.getSsoEmail.mockResolvedValue(testEmail); }); - it("should check feature flag", async () => { + it("should call updateSsoRequiredCache() and clearSsoEmail()", async () => { await service.run(userId); - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.PM22110_DisableAlternateLoginMethods, - ); - }); - - it("should get SSO email", async () => { - await service.run(userId); - - expect(ssoLoginService.getSsoEmail).toHaveBeenCalled(); - }); - - describe("given SSO email is not found", () => { - beforeEach(() => { - ssoLoginService.getSsoEmail.mockResolvedValue(null); - }); - - it("should log error and return early", async () => { - await service.run(userId); - - expect(logService.error).toHaveBeenCalledWith("SSO login email not found."); - expect(ssoLoginService.updateSsoRequiredCache).not.toHaveBeenCalled(); - }); - }); - - describe("given SSO email is found", () => { - beforeEach(() => { - ssoLoginService.getSsoEmail.mockResolvedValue(testEmail); - }); - - it("should call updateSsoRequiredCache() and clearSsoEmail()", async () => { - await service.run(userId); - - expect(ssoLoginService.updateSsoRequiredCache).toHaveBeenCalledWith(testEmail, userId); - expect(ssoLoginService.clearSsoEmail).toHaveBeenCalled(); - }); + expect(ssoLoginService.updateSsoRequiredCache).toHaveBeenCalledWith(testEmail, userId); + expect(ssoLoginService.clearSsoEmail).toHaveBeenCalled(); }); }); }); diff --git a/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts index 78003a4fca0..27d058c311a 100644 --- a/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts +++ b/libs/auth/src/common/services/login-success-handler/default-login-success-handler.service.ts @@ -1,5 +1,4 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; @@ -23,20 +22,14 @@ export class DefaultLoginSuccessHandlerService implements LoginSuccessHandlerSer await this.userAsymmetricKeysRegenerationService.regenerateIfNeeded(userId); await this.loginEmailService.clearLoginEmail(); - const disableAlternateLoginMethodsFlagEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM22110_DisableAlternateLoginMethods, - ); + const ssoLoginEmail = await this.ssoLoginService.getSsoEmail(); - if (disableAlternateLoginMethodsFlagEnabled) { - const ssoLoginEmail = await this.ssoLoginService.getSsoEmail(); - - if (!ssoLoginEmail) { - this.logService.error("SSO login email not found."); - return; - } - - await this.ssoLoginService.updateSsoRequiredCache(ssoLoginEmail, userId); - await this.ssoLoginService.clearSsoEmail(); + if (!ssoLoginEmail) { + this.logService.error("SSO login email not found."); + return; } + + await this.ssoLoginService.updateSsoRequiredCache(ssoLoginEmail, userId); + await this.ssoLoginService.clearSsoEmail(); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index d06a14d242f..17d5f4e9df5 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -16,7 +16,6 @@ export enum FeatureFlag { BlockClaimedDomainAccountCreation = "block-claimed-domain-account-creation", /* Auth */ - PM22110_DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods", PM23801_PrefetchPasswordPrelogin = "pm-23801-prefetch-password-prelogin", /* Autofill */ @@ -118,7 +117,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.VaultLoadingSkeletons]: FALSE, /* Auth */ - [FeatureFlag.PM22110_DisableAlternateLoginMethods]: FALSE, [FeatureFlag.PM23801_PrefetchPasswordPrelogin]: FALSE, /* Billing */ From 489eb4005780046a4f87719e5a30ae4449958fc2 Mon Sep 17 00:00:00 2001 From: neuronull <9162534+neuronull@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:02:22 -0700 Subject: [PATCH 216/249] Desktop Autotype fix IPC error handling (#17332) * Desktop Autotype fix IPC error handling * TS lint * sweep sweep: fix unecessary member name qualifier --- .../src/autofill/main/main-desktop-autotype.service.ts | 9 +++++++++ apps/desktop/src/autofill/models/autotype-errors.ts | 8 ++++++++ apps/desktop/src/autofill/preload.ts | 10 ++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 apps/desktop/src/autofill/models/autotype-errors.ts diff --git a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts index e33ab0d4c3b..4dcf05a4220 100644 --- a/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts +++ b/apps/desktop/src/autofill/main/main-desktop-autotype.service.ts @@ -5,6 +5,7 @@ import { LogService } from "@bitwarden/logging"; import { WindowMain } from "../../main/window.main"; import { stringIsNotUndefinedNullAndEmpty } from "../../utils"; +import { AutotypeMatchError } from "../models/autotype-errors"; import { AutotypeVaultData } from "../models/autotype-vault-data"; import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut"; @@ -56,6 +57,14 @@ export class MainDesktopAutotypeService { this.doAutotype(vaultData, this.autotypeKeyboardShortcut.getArrayFormat()); } }); + + ipcMain.on("autofill.completeAutotypeError", (_event, matchError: AutotypeMatchError) => { + this.logService.debug( + "autofill.completeAutotypeError", + "No match for window: " + matchError.windowTitle, + ); + this.logService.error("autofill.completeAutotypeError", matchError.errorMessage); + }); } disableAutotype() { diff --git a/apps/desktop/src/autofill/models/autotype-errors.ts b/apps/desktop/src/autofill/models/autotype-errors.ts new file mode 100644 index 00000000000..9e59b102302 --- /dev/null +++ b/apps/desktop/src/autofill/models/autotype-errors.ts @@ -0,0 +1,8 @@ +/** + * This error is surfaced when there is no matching + * vault item found. + */ +export interface AutotypeMatchError { + windowTitle: string; + errorMessage: string; +} diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts index 22b5cdf9463..e839ac223b7 100644 --- a/apps/desktop/src/autofill/preload.ts +++ b/apps/desktop/src/autofill/preload.ts @@ -5,6 +5,7 @@ import type { autofill } from "@bitwarden/desktop-napi"; import { Command } from "../platform/main/autofill/command"; import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; +import { AutotypeMatchError } from "./models/autotype-errors"; import { AutotypeVaultData } from "./models/autotype-vault-data"; export default { @@ -141,7 +142,7 @@ export default { ipcRenderer.on( "autofill.listenAutotypeRequest", ( - event, + _event, data: { windowTitle: string; }, @@ -150,10 +151,11 @@ export default { fn(windowTitle, (error, vaultData) => { if (error) { - ipcRenderer.send("autofill.completeError", { + const matchError: AutotypeMatchError = { windowTitle, - error: error.message, - }); + errorMessage: error.message, + }; + ipcRenderer.send("autofill.completeAutotypeError", matchError); return; } if (vaultData !== null) { From 13940a74ae88e146286f40e9c6dec164d11c21db Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann <mail@quexten.com> Date: Sat, 22 Nov 2025 11:53:45 +0100 Subject: [PATCH 217/249] 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 218/249] [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 <acoroiu@bitwarden.com> Date: Mon, 24 Nov 2025 16:36:23 +0100 Subject: [PATCH 219/249] [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 220/249] [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 221/249] 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 <Hinton@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:03:16 +0100 Subject: [PATCH 222/249] 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<OrganizationService> { private static _orgs = new BehaviorSubject<Organization[]>([]); @@ -49,6 +51,8 @@ class MockOrganizationService implements Partial<OrganizationService> { 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<ProviderService> { private static _providers = new BehaviorSubject<Provider[]>([]); 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<OrganizationService> { private static _orgs = new BehaviorSubject<Organization[]>([]); @@ -49,6 +51,8 @@ class MockOrganizationService implements Partial<OrganizationService> { 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<ProviderService> { private static _providers = new BehaviorSubject<Provider[]>([]); 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<ElementRef<HTMLDivElement>>("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<string | null>(null); - private readonly hintComponent = contentChild<BitHintComponent>(BitHintComponent); + private readonly hintComponent = contentChild<BitHintDirective>(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<any>) {} } @@ -69,7 +69,7 @@ export class TableScrollComponent /** Optional trackBy function. */ readonly trackBy = input<TrackByFunction<any> | 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 <bcunningham@bitwarden.com> Date: Mon, 24 Nov 2025 13:08:25 -0500 Subject: [PATCH 223/249] [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<ButtonType, string[]> = { "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 = { <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'primary'" [size]="size" [block]="block">Primary small</button> <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'secondary'" [size]="size" [block]="block">Secondary small</button> <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'danger'" [size]="size" [block]="block">Danger small</button> + <button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'dangerPrimary'" [size]="size" [block]="block">Danger Primary small</button> </div> `, }), 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 <mrobinson@bitwarden.com> Date: Mon, 24 Nov 2025 14:08:11 -0500 Subject: [PATCH 224/249] 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<boolean> { + 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<boolean> { + 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 225/249] [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 @@ <popup-page> + <bit-spotlight *ngIf="!(hasPremium$ | async)" persistent> + <span class="tw-text-xs" + >{{ "unlockFeaturesWithPremium" | i18n }} + <button + bitLink + buttonType="primary" + class="tw-text-xs" + type="button" + (click)="openUpgradeDialog()" + [title]="'upgradeNow' | i18n" + > + {{ "upgradeNow" | i18n }} + </button> + </span> + </bit-spotlight> <popup-header slot="header" pageTitle="{{ 'settings' | i18n }}"> <ng-container slot="end"> <app-pop-out></app-pop-out> @@ -20,7 +35,7 @@ <div class="tw-flex tw-items-center tw-justify-center"> <p class="tw-pr-2">{{ "autofill" | i18n }}</p> <span - *ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)" + *ngIf="!(isBrowserAutofillSettingOverridden$ | async) && (showAutofillBadge$ | async)" bitBadge variant="notification" [attr.aria-label]="'nudgeBadgeAria' | i18n" diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts b/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts new file mode 100644 index 00000000000..f51d514289e --- /dev/null +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.spec.ts @@ -0,0 +1,260 @@ +import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core"; +import { TestBed, waitForAsync } from "@angular/core/testing"; +import { RouterTestingModule } from "@angular/router/testing"; +import { mock, MockProxy } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom, of, Subject } from "rxjs"; + +import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components"; +import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; +import { AutofillBrowserSettingsService } from "@bitwarden/browser/autofill/services/autofill-browser-settings.service"; +import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { DialogService } from "@bitwarden/components"; +import { GlobalStateProvider } from "@bitwarden/state"; +import { FakeGlobalStateProvider } from "@bitwarden/state-test-utils"; + +import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; + +import { SettingsV2Component } from "./settings-v2.component"; + +@Component({ + selector: "app-current-account", + standalone: true, + template: "", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class CurrentAccountStubComponent {} + +describe("SettingsV2Component", () => { + let account$: BehaviorSubject<Account | null>; + let mockAccountService: Partial<AccountService>; + let mockBillingState: { hasPremiumFromAnySource$: jest.Mock }; + let mockNudges: { + showNudgeBadge$: jest.Mock; + dismissNudge: jest.Mock; + }; + let mockAutofillSettings: { + defaultBrowserAutofillDisabled$: Subject<boolean>; + isBrowserAutofillSettingOverridden: jest.Mock<Promise<boolean>>; + }; + let dialogService: MockProxy<DialogService>; + let openSpy: jest.SpyInstance; + + beforeEach(waitForAsync(async () => { + dialogService = mock<DialogService>(); + account$ = new BehaviorSubject<Account | null>(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<boolean>(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<PlatformUtilsService>() }, + { provide: AvatarService, useValue: mock<AvatarService>() }, + { provide: AuthService, useValue: mock<AuthService>() }, + ], + 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<Account> = 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<boolean> = 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 @@ </popup-header> <bit-item-group> - <bit-item *ngIf="!(canAccessPremium$ | async)"> - <a type="button" bit-item-content routerLink="/premium"> - {{ "premiumMembership" | i18n }} - <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> - </a> - </bit-item> <bit-item *ngIf=" (familySponsorshipAvailable$ | async) && diff --git a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts index 2f9fae43da7..0b896547008 100644 --- a/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/more-from-bitwarden-page-v2.component.ts @@ -1,13 +1,12 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { Observable, firstValueFrom, of, switchMap } from "rxjs"; +import { Observable, firstValueFrom, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { DialogService, ItemModule } from "@bitwarden/components"; @@ -32,14 +31,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co ], }) export class MoreFromBitwardenPageV2Component { - canAccessPremium$: Observable<boolean>; protected familySponsorshipAvailable$: Observable<boolean>; protected isFreeFamilyPolicyEnabled$: Observable<boolean>; protected hasSingleEnterpriseOrg$: Observable<boolean>; 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 @@ > <div class="tw-flex tw-justify-between tw-items-start tw-flex-grow"> <div> - <h2 bitTypography="h4" class="tw-font-medium !tw-mb-1">{{ title }}</h2> + <h2 *ngIf="title()" bitTypography="h4" class="tw-font-medium !tw-mb-1">{{ title() }}</h2> <p - *ngIf="subtitle" + *ngIf="subtitle()" class="tw-text-main tw-mb-0" bitTypography="body2" - [innerHTML]="subtitle" + [innerHTML]="subtitle()" ></p> - <ng-content *ngIf="!subtitle"></ng-content> + <ng-content *ngIf="!subtitle()"></ng-content> </div> <button type="button" bitIconButton="bwi-close" size="small" - *ngIf="!persistent" + *ngIf="!persistent()" (click)="handleDismiss()" class="-tw-me-2" [label]="'close' | i18n" @@ -28,10 +28,10 @@ bitButton type="button" buttonType="primary" - *ngIf="buttonText" + *ngIf="buttonText()" (click)="handleButtonClick($event)" > - {{ buttonText }} - <i *ngIf="buttonIcon" [ngClass]="buttonIcon" class="bwi tw-ml-1" aria-hidden="true"></i> + {{ buttonText() }} + <i *ngIf="buttonIcon()" [ngClass]="buttonIcon()" class="bwi tw-ml-1" aria-hidden="true"></i> </button> </div> 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<SpotlightComponent>; + 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: ` + <bit-spotlight> + <span class="tw-text-sm">Projected content</span> + </bit-spotlight> + `, + }) + class HostWithProjectionComponent {} + + let hostFixture: ComponentFixture<HostWithProjectionComponent>; + + 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: ` + <!-- bare persistent attribute --> + <bit-spotlight *ngIf="mode === 'bare'" persistent></bit-spotlight> + + <!-- no persistent attribute --> + <bit-spotlight *ngIf="mode === 'none'"></bit-spotlight> + + <!-- explicit persistent="false" --> + <bit-spotlight *ngIf="mode === 'falseStr'" persistent="false"></bit-spotlight> + `, + }) + class BooleanHostComponent { + mode: "bare" | "none" | "falseStr" = "bare"; + } + + let boolFixture: ComponentFixture<BooleanHostComponent>; + 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<string>(); // 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<string>(); // 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<string>(); + // 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<void>(); - // 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<string>(); + readonly onDismiss = output<void>(); + readonly onButtonClick = output<MouseEvent>(); handleButtonClick(event: MouseEvent): void { this.onButtonClick.emit(event); From e6d6f8d266d325289229b8ce139919aa38ba042f Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:11:21 +0100 Subject: [PATCH 226/249] 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 227/249] [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 <Hinton@users.noreply.github.com> --- 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 <acoroiu@bitwarden.com> Date: Tue, 25 Nov 2025 14:48:25 +0100 Subject: [PATCH 228/249] [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<typeof BitwardenClient> - ): Promise<BitwardenClient> { + ...args: ConstructorParameters<typeof PasswordManagerClient> + ): Promise<PasswordManagerClient> { 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<typeof BitwardenClient> - ): Promise<BitwardenClient>; + ...args: ConstructorParameters<typeof PasswordManagerClient> + ): Promise<PasswordManagerClient>; } 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<BitwardenClient>; + abstract client$: Observable<PasswordManagerClient>; /** * 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<Rc<BitwardenClient>>; + abstract userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>>; /** * 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<typeof sdk.BitwardenClient> - ): Promise<sdk.BitwardenClient> { - return Promise.resolve(new sdk.BitwardenClient(...args)); + ...args: ConstructorParameters<typeof sdk.PasswordManagerClient> + ): Promise<sdk.PasswordManagerClient> { + 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<BitwardenClient>; + let mockClient!: MockProxy<PasswordManagerClient>; 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<Rc<BitwardenClient> | undefined>(undefined); - const subject_2 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined); + const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined); + const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | 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<Rc<BitwardenClient> | undefined>(undefined); - const subject_2 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined); + const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined); + const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | 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<BitwardenClient>(); + const mockClient = mock<PasswordManagerClient>(); 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<BitwardenClient> { - const client = mock<BitwardenClient>(); +function createMockClient(): MockProxy<PasswordManagerClient> { + const client = mock<PasswordManagerClient>(); 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<BitwardenClient> | typeof UnsetClient; + [userId: UserId]: Rc<PasswordManagerClient> | typeof UnsetClient; }>({}); - private sdkClientCache = new Map<UserId, Observable<Rc<BitwardenClient>>>(); + private sdkClientCache = new Map<UserId, Observable<Rc<PasswordManagerClient>>>(); 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<Rc<BitwardenClient>> { + userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> { 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<BitwardenClient>; + return clients[userId] as Rc<PasswordManagerClient>; }), 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<Rc<BitwardenClient>> { + private internalClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> { 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<Rc<BitwardenClient>>((subscriber) => { + return new Observable<Rc<PasswordManagerClient>>((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<BitwardenClient> | undefined; + let client: Rc<PasswordManagerClient> | 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<typeof BitwardenClient> - ): Promise<BitwardenClient> { + ...args: ConstructorParameters<typeof PasswordManagerClient> + ): Promise<PasswordManagerClient> { 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<BitwardenClient> | undefined; + [userId: UserId]: Rc<PasswordManagerClient> | undefined; }>({}); - private _client$ = new BehaviorSubject(mockDeep<BitwardenClient>()); + private _client$ = new BehaviorSubject(mockDeep<PasswordManagerClient>()); client$ = this._client$.asObservable(); version$ = new BehaviorSubject("0.0.1-test").asObservable(); - userClient$(userId: UserId): Observable<Rc<BitwardenClient>> { + userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> { return this.userClients$.pipe( takeWhile((clients) => clients[userId] !== undefined, false), - map((clients) => clients[userId] as Rc<BitwardenClient>), + map((clients) => clients[userId] as Rc<PasswordManagerClient>), 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<BitwardenClient> { + get client(): DeepMockProxy<PasswordManagerClient> { 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<BitwardenClient>(); + const client = mockDeep<PasswordManagerClient>(); 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 229/249] 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 @@ <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> {{ "copyUsername" | i18n }} </button> - <button bitMenuItem type="button" appCopyField="password" [cipher]="cipher"> - <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> - {{ "copyPassword" | i18n }} - </button> + @if (cipher.viewPassword) { + <button bitMenuItem type="button" appCopyField="password" [cipher]="cipher"> + <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> + {{ "copyPassword" | i18n }} + </button> + } <button bitMenuItem type="button" appCopyField="totp" [cipher]="cipher"> <i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i> {{ "copyVerificationCode" | i18n }} diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts new file mode 100644 index 00000000000..d5f7b54f37a --- /dev/null +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts @@ -0,0 +1,144 @@ +import { OverlayContainer } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { RouterModule } from "@angular/router"; +import { mock } from "jest-mock-extended"; +import { BehaviorSubject } from "rxjs"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; +import { IconButtonModule, MenuModule } from "@bitwarden/components"; +import { CopyCipherFieldDirective, CopyCipherFieldService } from "@bitwarden/vault"; + +import { OrganizationNameBadgeComponent } from "../../individual-vault/organization-badge/organization-name-badge.component"; + +import { VaultCipherRowComponent } from "./vault-cipher-row.component"; + +// eslint-disable-next-line no-console +const originalError = console.error; + +// eslint-disable-next-line no-console +console.error = (...args) => { + if ( + typeof args[0] === "object" && + (args[0] as Error).message.includes("Could not parse CSS stylesheet") + ) { + // Opening the overlay container in tests causes stylesheets to be parsed, + // which can lead to JSDOM unable to parse CSS errors. These can be ignored safely. + return; + } + originalError(...args); +}; + +describe("VaultCipherRowComponent", () => { + let component: VaultCipherRowComponent<CipherViewLike>; + let fixture: ComponentFixture<VaultCipherRowComponent<CipherViewLike>>; + let overlayContainer: OverlayContainer; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [VaultCipherRowComponent, OrganizationNameBadgeComponent], + imports: [ + CommonModule, + RouterModule.forRoot([]), + MenuModule, + IconButtonModule, + JslibModule, + CopyCipherFieldDirective, + ], + providers: [ + { provide: I18nService, useValue: { t: (key: string) => key } }, + { + provide: EnvironmentService, + useValue: { environment$: new BehaviorSubject({}).asObservable() }, + }, + { + provide: DomainSettingsService, + useValue: { showFavicons$: new BehaviorSubject(false).asObservable() }, + }, + { provide: CopyCipherFieldService, useValue: mock<CopyCipherFieldService>() }, + { provide: AccountService, useValue: mock<AccountService>() }, + { provide: CipherService, useValue: mock<CipherService>() }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(VaultCipherRowComponent); + component = fixture.componentInstance; + overlayContainer = TestBed.inject(OverlayContainer); + }); + + afterEach(() => { + overlayContainer?.ngOnDestroy(); + }); + + afterAll(() => { + // eslint-disable-next-line no-console + console.error = originalError; + }); + + describe("copy password visibility", () => { + let loginCipher: CipherView; + + beforeEach(() => { + loginCipher = new CipherView(); + loginCipher.id = "cipher-1"; + loginCipher.name = "Test Login"; + loginCipher.type = CipherType.Login; + loginCipher.login = new LoginView(); + loginCipher.login.password = "test-password"; + loginCipher.organizationId = undefined; + loginCipher.deletedDate = null; + loginCipher.archivedDate = null; + + component.cipher = loginCipher; + component.disabled = false; + }); + + const openMenuAndGetContent = (): string => { + fixture.detectChanges(); + + const menuTrigger = fixture.nativeElement.querySelector( + 'button[biticonbutton="bwi-ellipsis-v"]', + ) as HTMLButtonElement; + expect(menuTrigger).toBeTruthy(); + + menuTrigger.click(); + fixture.detectChanges(); + + return overlayContainer.getContainerElement().innerHTML; + }; + + it("renders copy password button in menu when viewPassword is true", () => { + component.cipher.viewPassword = true; + + const overlayContent = openMenuAndGetContent(); + + expect(overlayContent).toContain('appcopyfield="password"'); + expect(overlayContent).toContain("copyPassword"); + }); + + it("does not render copy password button in menu when viewPassword is false", () => { + component.cipher.viewPassword = false; + + const overlayContent = openMenuAndGetContent(); + + expect(overlayContent).not.toContain('appcopyfield="password"'); + }); + + it("does not render copy password button in menu when viewPassword is undefined", () => { + component.cipher.viewPassword = undefined; + + const overlayContent = openMenuAndGetContent(); + + expect(overlayContent).not.toContain('appcopyfield="password"'); + }); + }); +}); From d16c25e759836e299f402b81784b524e99af16df Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:02:51 -0500 Subject: [PATCH 230/249] chore(docs) Add comments from contributing docs to master password types in code * Added comments from contributing docs. * Grammatical changes. --- .../master-password/types/master-password.types.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/common/src/key-management/master-password/types/master-password.types.ts b/libs/common/src/key-management/master-password/types/master-password.types.ts index 19c8c49c119..5ba22905140 100644 --- a/libs/common/src/key-management/master-password/types/master-password.types.ts +++ b/libs/common/src/key-management/master-password/types/master-password.types.ts @@ -25,7 +25,10 @@ export type MasterPasswordSalt = Opaque<string, "MasterPasswordSalt">; export type MasterKeyWrappedUserKey = Opaque<EncString, "MasterKeyWrappedUserKey">; /** - * The data required to unlock with the master password. + * Encapsulates the data needed to unlock a vault using a master password. + * It contains the masterKeyWrappedUserKey along with the KDF settings and salt used to derive the master key. + * It is currently backwards compatible to master-key based unlock, but this will not be the case in the future. + * Features relating to master-password-based unlock should use this abstraction. */ export class MasterPasswordUnlockData { constructor( @@ -66,7 +69,9 @@ export class MasterPasswordUnlockData { } /** - * The data required to authenticate with the master password. + * Encapsulates the data required to authenticate using a master password. + * It contains the masterPasswordAuthenticationHash, along with the KDF settings and salt used to derive it. + * The encapsulated abstraction prevents authentication issues resulting from unsynchronized state. */ export type MasterPasswordAuthenticationData = { salt: MasterPasswordSalt; From 568183bacd084490f1970c8b495005306791dcf4 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Tue, 25 Nov 2025 10:18:43 -0500 Subject: [PATCH 231/249] fix disabled cursor styles (#17656) --- libs/components/src/checkbox/checkbox.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index 61d5bb7251c..2501b85f617 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -22,6 +22,7 @@ export class CheckboxComponent implements BitFormControlAbstraction { "tw-relative", "tw-transition", "tw-cursor-pointer", + "disabled:tw-cursor-default", "tw-inline-block", "tw-align-sub", "tw-flex-none", // Flexbox fix for bit-form-control @@ -62,7 +63,7 @@ export class CheckboxComponent implements BitFormControlAbstraction { "[&:not(bit-form-control_*)]:focus-visible:before:tw-ring-offset-2", "[&:not(bit-form-control_*)]:focus-visible:before:tw-ring-primary-600", - "disabled:before:tw-cursor-auto", + "disabled:before:tw-cursor-default", "disabled:before:tw-border", "disabled:before:hover:tw-border", "disabled:before:tw-bg-secondary-100", From 57946f6406472ec275a8d102754aabb351d3f683 Mon Sep 17 00:00:00 2001 From: SmithThe4th <gsmith@bitwarden.com> Date: Tue, 25 Nov 2025 10:37:28 -0500 Subject: [PATCH 232/249] Fixed invalid cipher remprompt values (#17513) --- libs/common/src/vault/models/domain/cipher.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 5739a9a50a7..bbbf6a6a054 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -414,7 +414,10 @@ export class Cipher extends Domain implements Decryptable<CipherView> { creationDate: this.creationDate.toISOString(), deletedDate: this.deletedDate?.toISOString(), archivedDate: this.archivedDate?.toISOString(), - reprompt: this.reprompt, + reprompt: + this.reprompt === CipherRepromptType.Password + ? CipherRepromptType.Password + : CipherRepromptType.None, // Initialize all cipher-type-specific properties as undefined login: undefined, identity: undefined, From 540da69daf2d431719113e96078f03ea85caa0a4 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham <bcunningham@bitwarden.com> Date: Tue, 25 Nov 2025 11:04:37 -0500 Subject: [PATCH 233/249] [CL-761] Enable strict template typechecking (#17334) * enable strict template typechecking * add callout component to module * fixing popup action types * fixing cipher item copy types * fix archive cipher type * fixing trash list items types * fix remaining trash list item type errors * use CipherViewLike as correct type * change popup back directive to attribute selector * allow undefined in popupBackAction handler * Remove undefined from type * fix error with firefox commercial build --------- Co-authored-by: Vicki League <vleague@bitwarden.com> --- apps/browser/src/popup/app.module.ts | 2 +- .../item-copy-actions.component.ts | 8 ++-- .../popup/settings/archive.component.html | 4 +- .../vault/popup/settings/archive.component.ts | 30 +++++++----- .../trash-list-items-container.component.html | 6 +-- .../trash-list-items-container.component.ts | 46 +++++++++++++++---- apps/browser/tsconfig.json | 3 ++ .../components/can-delete-cipher.directive.ts | 4 +- .../components/copy-cipher-field.directive.ts | 2 +- libs/vault/src/index.ts | 6 ++- .../src/services/copy-cipher-field.service.ts | 6 +++ 11 files changed, 84 insertions(+), 33 deletions(-) diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index ec35fc7c554..71846cc6444 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -92,7 +92,7 @@ import "../platform/popup/locales"; TabsV2Component, RemovePasswordComponent, ], - exports: [], + exports: [CalloutModule], providers: [CurrencyPipe, DatePipe], bootstrap: [AppComponent], }) diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts index 2e2ee5cd56b..e24db60a55a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.ts @@ -10,7 +10,7 @@ import { } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { IconButtonModule, ItemModule, MenuModule } from "@bitwarden/components"; import { CopyableCipherFields } from "@bitwarden/sdk-internal"; -import { CopyAction, CopyCipherFieldDirective } from "@bitwarden/vault"; +import { CopyFieldAction, CopyCipherFieldDirective } from "@bitwarden/vault"; import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; @@ -18,7 +18,7 @@ type CipherItem = { /** Translation key for the respective value */ key: string; /** Property key on `CipherView` to retrieve the copy value */ - field: CopyAction; + field: CopyFieldAction; }; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -48,7 +48,7 @@ export class ItemCopyActionsComponent { * singleCopyableLogin uses appCopyField instead of appCopyClick. This allows for the TOTP * code to be copied correctly. See #14167 */ - get singleCopyableLogin() { + get singleCopyableLogin(): CipherItem | null { const loginItems: CipherItem[] = [ { key: "copyUsername", field: "username" }, { key: "copyPassword", field: "password" }, @@ -62,7 +62,7 @@ export class ItemCopyActionsComponent { ) { return { key: this.i18nService.t("copyUsername"), - field: "username", + field: "username" as const, }; } return this.findSingleCopyableItem(loginItems); diff --git a/apps/browser/src/vault/popup/settings/archive.component.html b/apps/browser/src/vault/popup/settings/archive.component.html index faaf0243fc7..059d636c60d 100644 --- a/apps/browser/src/vault/popup/settings/archive.component.html +++ b/apps/browser/src/vault/popup/settings/archive.component.html @@ -27,10 +27,10 @@ <app-vault-icon [cipher]="cipher"></app-vault-icon> </div> <span data-testid="item-name">{{ cipher.name }}</span> - @if (cipher.hasAttachments) { + @if (CipherViewLikeUtils.hasAttachments(cipher)) { <i class="bwi bwi-paperclip bwi-sm" [appA11yTitle]="'attachments' | i18n"></i> } - <span slot="secondary">{{ cipher.subTitle }}</span> + <span slot="secondary">{{ CipherViewLikeUtils.subtitle(cipher) }}</span> </button> <bit-item-action slot="end"> <button diff --git a/apps/browser/src/vault/popup/settings/archive.component.ts b/apps/browser/src/vault/popup/settings/archive.component.ts index 58925eda428..b1c78444a3f 100644 --- a/apps/browser/src/vault/popup/settings/archive.component.ts +++ b/apps/browser/src/vault/popup/settings/archive.component.ts @@ -11,7 +11,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + CipherViewLike, + CipherViewLikeUtils, +} from "@bitwarden/common/vault/utils/cipher-view-like-utils"; import { DialogService, IconButtonModule, @@ -71,12 +74,14 @@ export class ArchiveComponent { switchMap((userId) => this.cipherArchiveService.archivedCiphers$(userId)), ); + protected CipherViewLikeUtils = CipherViewLikeUtils; + protected loading$ = this.archivedCiphers$.pipe( map(() => false), startWith(true), ); - async view(cipher: CipherView) { + async view(cipher: CipherViewLike) { if (!(await this.canInteract(cipher))) { return; } @@ -86,7 +91,7 @@ export class ArchiveComponent { }); } - async edit(cipher: CipherView) { + async edit(cipher: CipherViewLike) { if (!(await this.canInteract(cipher))) { return; } @@ -96,7 +101,7 @@ export class ArchiveComponent { }); } - async delete(cipher: CipherView) { + async delete(cipher: CipherViewLike) { if (!(await this.canInteract(cipher))) { return; } @@ -113,7 +118,7 @@ export class ArchiveComponent { const activeUserId = await firstValueFrom(this.userId$); try { - await this.cipherService.softDeleteWithServer(cipher.id, activeUserId); + await this.cipherService.softDeleteWithServer(cipher.id as string, activeUserId); } catch (e) { this.logService.error(e); return; @@ -125,13 +130,16 @@ export class ArchiveComponent { }); } - async unarchive(cipher: CipherView) { + async unarchive(cipher: CipherViewLike) { if (!(await this.canInteract(cipher))) { return; } const activeUserId = await firstValueFrom(this.userId$); - await this.cipherArchiveService.unarchiveWithServer(cipher.id as CipherId, activeUserId); + await this.cipherArchiveService.unarchiveWithServer( + cipher.id as unknown as CipherId, + activeUserId, + ); this.toastService.showToast({ variant: "success", @@ -139,12 +147,12 @@ export class ArchiveComponent { }); } - async clone(cipher: CipherView) { + async clone(cipher: CipherViewLike) { if (!(await this.canInteract(cipher))) { return; } - if (cipher.login?.hasFido2Credentials) { + if (CipherViewLikeUtils.hasFido2Credentials(cipher)) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "passkeyNotCopied" }, content: { key: "passkeyNotCopiedAlert" }, @@ -171,8 +179,8 @@ export class ArchiveComponent { * @param cipher * @private */ - private canInteract(cipher: CipherView) { - if (cipher.decryptionFailure) { + private canInteract(cipher: CipherViewLike) { + if (CipherViewLikeUtils.decryptionFailure(cipher)) { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [cipher.id as CipherId], }); diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html index d1e70390844..eff8a04b966 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.html @@ -25,11 +25,11 @@ [appA11yTitle]="orgIconTooltip(cipher)" ></i> <i - *ngIf="cipher.hasAttachments" + *ngIf="hasAttachments(cipher)" class="bwi bwi-paperclip bwi-sm" [appA11yTitle]="'attachments' | i18n" ></i> - <span slot="secondary">{{ cipher.subTitle }}</span> + <span slot="secondary">{{ getSubtitle(cipher) }}</span> </button> <ng-container slot="end" *ngIf="cipher.permissions.restore"> <bit-item-action> @@ -45,7 +45,7 @@ type="button" bitMenuItem (click)="restore(cipher)" - *ngIf="!cipher.decryptionFailure" + *ngIf="!hasDecryptionFailure(cipher)" > {{ "restore" | i18n }} </button> diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index 70ba6842a0d..bad6011b2d8 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -12,7 +12,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService, IconButtonModule, @@ -85,10 +84,40 @@ export class TrashListItemsContainerComponent { return collections[0]?.name; } - async restore(cipher: CipherView) { + /** + * Check if a cipher has attachments. CipherView has a hasAttachments getter, + * while CipherListView has an attachments count property. + */ + hasAttachments(cipher: PopupCipherViewLike): boolean { + if ("hasAttachments" in cipher) { + return cipher.hasAttachments; + } + return cipher.attachments > 0; + } + + /** + * Get the subtitle for a cipher. CipherView has a subTitle getter, + * while CipherListView has a subtitle property. + */ + getSubtitle(cipher: PopupCipherViewLike): string | undefined { + if ("subTitle" in cipher) { + return cipher.subTitle; + } + return cipher.subtitle; + } + + /** + * Check if a cipher has a decryption failure. CipherView has this property, + * while CipherListView does not. + */ + hasDecryptionFailure(cipher: PopupCipherViewLike): boolean { + return "decryptionFailure" in cipher && cipher.decryptionFailure; + } + + async restore(cipher: PopupCipherViewLike) { try { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.cipherService.restoreWithServer(cipher.id, activeUserId); + await this.cipherService.restoreWithServer(cipher.id as string, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ @@ -101,7 +130,7 @@ export class TrashListItemsContainerComponent { } } - async delete(cipher: CipherView) { + async delete(cipher: PopupCipherViewLike) { const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); if (!repromptPassed) { @@ -120,7 +149,7 @@ export class TrashListItemsContainerComponent { try { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.cipherService.deleteWithServer(cipher.id, activeUserId); + await this.cipherService.deleteWithServer(cipher.id as string, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ @@ -133,8 +162,9 @@ export class TrashListItemsContainerComponent { } } - async onViewCipher(cipher: CipherView) { - if (cipher.decryptionFailure) { + async onViewCipher(cipher: PopupCipherViewLike) { + // CipherListView doesn't have decryptionFailure, so we use optional chaining + if ("decryptionFailure" in cipher && cipher.decryptionFailure) { DecryptionFailureDialogComponent.open(this.dialogService, { cipherIds: [cipher.id as CipherId], }); @@ -147,7 +177,7 @@ export class TrashListItemsContainerComponent { } await this.router.navigate(["/view-cipher"], { - queryParams: { cipherId: cipher.id, type: cipher.type }, + queryParams: { cipherId: cipher.id as string, type: cipher.type }, }); } } diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index 0fd6cac4230..6fb9dfbe46b 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../tsconfig.base", + "angularCompilerOptions": { + "strictTemplates": true + }, "include": [ "src", "../../libs/common/src/autofill/constants", diff --git a/libs/vault/src/components/can-delete-cipher.directive.ts b/libs/vault/src/components/can-delete-cipher.directive.ts index 8ab59f9d647..f88e38049da 100644 --- a/libs/vault/src/components/can-delete-cipher.directive.ts +++ b/libs/vault/src/components/can-delete-cipher.directive.ts @@ -1,8 +1,8 @@ import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; +import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils"; /** * Only shows the element if the user can delete the cipher. @@ -15,7 +15,7 @@ export class CanDeleteCipherDirective implements OnDestroy { // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals - @Input("appCanDeleteCipher") set cipher(cipher: CipherView) { + @Input("appCanDeleteCipher") set cipher(cipher: CipherViewLike) { this.viewContainer.clear(); this.cipherAuthorizationService diff --git a/libs/vault/src/components/copy-cipher-field.directive.ts b/libs/vault/src/components/copy-cipher-field.directive.ts index b4b1941273f..52a4f59e7a2 100644 --- a/libs/vault/src/components/copy-cipher-field.directive.ts +++ b/libs/vault/src/components/copy-cipher-field.directive.ts @@ -36,7 +36,7 @@ export class CopyCipherFieldDirective implements OnChanges { alias: "appCopyField", required: true, }) - action!: Exclude<CopyAction, "hiddenField">; + action!: CopyAction; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals // eslint-disable-next-line @angular-eslint/prefer-signals diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index ccd830cd34e..93a72ba14e0 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -3,7 +3,11 @@ export { AtRiskPasswordCalloutData, } from "./services/at-risk-password-callout.service"; export { PasswordRepromptService } from "./services/password-reprompt.service"; -export { CopyCipherFieldService, CopyAction } from "./services/copy-cipher-field.service"; +export { + CopyCipherFieldService, + CopyAction, + CopyFieldAction, +} from "./services/copy-cipher-field.service"; export { CopyCipherFieldDirective } from "./components/copy-cipher-field.directive"; export { OrgIconDirective } from "./components/org-icon.directive"; export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive"; diff --git a/libs/vault/src/services/copy-cipher-field.service.ts b/libs/vault/src/services/copy-cipher-field.service.ts index 0a7d9e1f68d..539c81b7be3 100644 --- a/libs/vault/src/services/copy-cipher-field.service.ts +++ b/libs/vault/src/services/copy-cipher-field.service.ts @@ -35,6 +35,12 @@ export type CopyAction = | "publicKey" | "keyFingerprint"; +/** + * Copy actions that can be used with the appCopyField directive. + * Excludes "hiddenField" which requires special handling. + */ +export type CopyFieldAction = Exclude<CopyAction, "hiddenField">; + type CopyActionInfo = { /** * The i18n key for the type of field being copied. Will be used to display a toast message. From c04c1757ea6de993a6c808fdda7d64cdf0651425 Mon Sep 17 00:00:00 2001 From: Ben Brooks <56796209+bensbits91@users.noreply.github.com> Date: Tue, 25 Nov 2025 08:06:03 -0800 Subject: [PATCH 234/249] Revert "Lets shadow DOM check signal page update (#16114)" (commit 6129ca536686cfceb385e9b87f6c569f0ecf9558) (#17503) Signed-off-by: Ben Brooks <bbrooks@bitwarden.com> --- .../abstractions/dom-query.service.ts | 2 +- .../collect-autofill-content.service.spec.ts | 31 +------------------ .../collect-autofill-content.service.ts | 13 ++------ .../services/dom-query.service.spec.ts | 2 ++ .../autofill/services/dom-query.service.ts | 9 ++++-- 5 files changed, 13 insertions(+), 44 deletions(-) diff --git a/apps/browser/src/autofill/services/abstractions/dom-query.service.ts b/apps/browser/src/autofill/services/abstractions/dom-query.service.ts index 32809573223..da7354403e5 100644 --- a/apps/browser/src/autofill/services/abstractions/dom-query.service.ts +++ b/apps/browser/src/autofill/services/abstractions/dom-query.service.ts @@ -6,5 +6,5 @@ export interface DomQueryService { mutationObserver?: MutationObserver, forceDeepQueryAttempt?: boolean, ): T[]; - checkPageContainsShadowDom(): boolean; + checkPageContainsShadowDom(): void; } diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts index 9ee329fa150..66a692dbe20 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.spec.ts @@ -395,7 +395,7 @@ describe("CollectAutofillContentService", () => { }); }); - it("sets the noFieldsFound property to true if the page has no forms or fields", async function () { + it("sets the noFieldsFond property to true if the page has no forms or fields", async function () { document.body.innerHTML = ""; collectAutofillContentService["noFieldsFound"] = false; jest.spyOn(collectAutofillContentService as any, "buildAutofillFormsData"); @@ -2649,33 +2649,4 @@ describe("CollectAutofillContentService", () => { ); }); }); - - describe("processMutations", () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.runOnlyPendingTimers(); - jest.useRealTimers(); - }); - - it("will require an update to page details if shadow DOM is present", () => { - jest - .spyOn(domQueryService as any, "checkPageContainsShadowDom") - .mockImplementationOnce(() => true); - - collectAutofillContentService["requirePageDetailsUpdate"] = jest.fn(); - - collectAutofillContentService["mutationsQueue"] = [[], []]; - - collectAutofillContentService["processMutations"](); - - jest.runOnlyPendingTimers(); - - expect(domQueryService.checkPageContainsShadowDom).toHaveBeenCalled(); - expect(collectAutofillContentService["mutationsQueue"]).toHaveLength(0); - expect(collectAutofillContentService["requirePageDetailsUpdate"]).toHaveBeenCalled(); - }); - }); }); diff --git a/apps/browser/src/autofill/services/collect-autofill-content.service.ts b/apps/browser/src/autofill/services/collect-autofill-content.service.ts index 47b1c9ea6df..6f2c00a4dd4 100644 --- a/apps/browser/src/autofill/services/collect-autofill-content.service.ts +++ b/apps/browser/src/autofill/services/collect-autofill-content.service.ts @@ -997,13 +997,6 @@ export class CollectAutofillContentService implements CollectAutofillContentServ * within an idle callback to help with performance and prevent excessive updates. */ private processMutations = () => { - // If the page contains shadow DOM, we require a page details update from the autofill service. - // Will wait for an idle moment on main thread to execute, unless timeout has passed. - requestIdleCallbackPolyfill( - () => this.domQueryService.checkPageContainsShadowDom() && this.requirePageDetailsUpdate(), - { timeout: 500 }, - ); - const queueLength = this.mutationsQueue.length; for (let queueIndex = 0; queueIndex < queueLength; queueIndex++) { @@ -1026,13 +1019,13 @@ export class CollectAutofillContentService implements CollectAutofillContentServ * Triggers several flags that indicate that a collection of page details should * occur again on a subsequent call after a mutation has been observed in the DOM. */ - private requirePageDetailsUpdate = () => { + private flagPageDetailsUpdateIsRequired() { this.domRecentlyMutated = true; if (this.autofillOverlayContentService) { this.autofillOverlayContentService.pageDetailsUpdateRequired = true; } this.noFieldsFound = false; - }; + } /** * Processes all mutation records encountered by the mutation observer. @@ -1060,7 +1053,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ (this.isAutofillElementNodeMutated(mutation.removedNodes, true) || this.isAutofillElementNodeMutated(mutation.addedNodes)) ) { - this.requirePageDetailsUpdate(); + this.flagPageDetailsUpdateIsRequired(); return; } diff --git a/apps/browser/src/autofill/services/dom-query.service.spec.ts b/apps/browser/src/autofill/services/dom-query.service.spec.ts index 87645c98a45..53862aef735 100644 --- a/apps/browser/src/autofill/services/dom-query.service.spec.ts +++ b/apps/browser/src/autofill/services/dom-query.service.spec.ts @@ -72,6 +72,7 @@ describe("DomQueryService", () => { }); it("queries form field elements that are nested within multiple ShadowDOM elements", () => { + domQueryService["pageContainsShadowDom"] = true; const root = document.createElement("div"); const shadowRoot1 = root.attachShadow({ mode: "open" }); const root2 = document.createElement("div"); @@ -94,6 +95,7 @@ describe("DomQueryService", () => { }); it("will fallback to using the TreeWalker API if a depth larger than 4 ShadowDOM elements is encountered", () => { + domQueryService["pageContainsShadowDom"] = true; const root = document.createElement("div"); const shadowRoot1 = root.attachShadow({ mode: "open" }); const root2 = document.createElement("div"); diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 1b0c5681ff0..932bbe47f90 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -78,9 +78,8 @@ export class DomQueryService implements DomQueryServiceInterface { /** * Checks if the page contains any shadow DOM elements. */ - checkPageContainsShadowDom = (): boolean => { + checkPageContainsShadowDom = (): void => { this.pageContainsShadowDom = this.queryShadowRoots(globalThis.document.body, true).length > 0; - return this.pageContainsShadowDom; }; /** @@ -109,7 +108,7 @@ export class DomQueryService implements DomQueryServiceInterface { ): T[] { let elements = this.queryElements<T>(root, queryString); - const shadowRoots = this.pageContainsShadowDom ? this.recursivelyQueryShadowRoots(root) : []; + const shadowRoots = this.recursivelyQueryShadowRoots(root); for (let index = 0; index < shadowRoots.length; index++) { const shadowRoot = shadowRoots[index]; elements = elements.concat(this.queryElements<T>(shadowRoot, queryString)); @@ -152,6 +151,10 @@ export class DomQueryService implements DomQueryServiceInterface { root: Document | ShadowRoot | Element, depth: number = 0, ): ShadowRoot[] { + if (!this.pageContainsShadowDom) { + return []; + } + if (depth >= MAX_DEEP_QUERY_RECURSION_DEPTH) { throw new Error("Max recursion depth reached"); } From cf6569bfea7516cf3e86f2854d5c61c17a547983 Mon Sep 17 00:00:00 2001 From: Dave <3836813+enmande@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:23:22 -0500 Subject: [PATCH 235/249] feat(user-decryption-options) [PM-26413]: Remove ActiveUserState from UserDecryptionOptionsService (#16894) * feat(user-decryption-options) [PM-26413]: Update UserDecryptionOptionsService and tests to use UserId-only APIs. * feat(user-decryption-options) [PM-26413]: Update InternalUserDecryptionOptionsService call sites to use UserId-only API. * feat(user-decryption-options) [PM-26413] Update userDecryptionOptions$ call sites to use the UserId-only API. * feat(user-decryption-options) [PM-26413]: Update additional call sites. * feat(user-decryption-options) [PM-26413]: Update dependencies and an additional call site. * feat(user-verification-service) [PM-26413]: Replace where allowed by unrestricted imports invocation of UserVerificationService.hasMasterPassword (deprecated) with UserDecryptionOptions.hasMasterPasswordById$. Additional work to complete as tech debt tracked in PM-27009. * feat(user-decryption-options) [PM-26413]: Update for non-null strict adherence. * feat(user-decryption-options) [PM-26413]: Update type safety and defensive returns. * chore(user-decryption-options) [PM-26413]: Comment cleanup. * feat(user-decryption-options) [PM-26413]: Update tests. * feat(user-decryption-options) [PM-26413]: Standardize null-checking on active account id for new API consumption. * feat(vault-timeout-settings-service) [PM-26413]: Add test cases to illustrate null active account from AccountService. * fix(fido2-user-verification-service-spec) [PM-26413]: Update test harness to use FakeAccountService. * fix(downstream-components) [PM-26413]: Prefer use of the getUserId operator in all authenticated contexts for user id provided to UserDecryptionOptionsService. --------- Co-authored-by: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com> --- .../browser/src/background/main.background.ts | 7 +- .../src/popup/services/services.module.ts | 8 +- .../fido2-user-verification.service.spec.ts | 31 ++++--- .../fido2-user-verification.service.ts | 16 +++- .../service-container/service-container.ts | 5 +- ...sktop-set-initial-password.service.spec.ts | 4 +- .../web-set-initial-password.service.spec.ts | 4 +- .../settings/account/account.component.ts | 10 +- .../password-settings.component.ts | 7 +- .../security/security-keys.component.ts | 22 ++++- .../settings/security/security.component.ts | 14 ++- .../organization-options.component.ts | 5 +- ...initial-password.service.implementation.ts | 7 +- ...fault-set-initial-password.service.spec.ts | 10 +- .../src/services/jslib-services.module.ts | 3 +- .../login-decryption-options.component.ts | 2 +- libs/auth/src/angular/sso/sso.component.ts | 2 +- .../two-factor-auth.component.spec.ts | 4 +- .../two-factor-auth.component.ts | 2 +- ...-decryption-options.service.abstraction.ts | 35 ++++--- .../login-strategies/login.strategy.spec.ts | 3 +- .../common/login-strategies/login.strategy.ts | 3 +- .../sso-login.strategy.spec.ts | 4 +- .../login-strategies/sso-login.strategy.ts | 2 +- .../user-decryption-options.service.spec.ts | 93 ++++++++++--------- .../user-decryption-options.service.ts | 42 ++++----- .../user-verification.service.abstraction.ts | 3 + .../user-verification.service.spec.ts | 23 +---- .../user-verification.service.ts | 19 ++-- .../device-trust.service.implementation.ts | 17 +++- .../services/device-trust.service.spec.ts | 3 +- .../vault-timeout-settings.service.spec.ts | 25 ++++- .../vault-timeout-settings.service.ts | 17 ++-- 33 files changed, 280 insertions(+), 172 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index fecc47af981..78b5e323798 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -728,7 +728,9 @@ export default class MainBackground { this.appIdService = new AppIdService(this.storageService, this.logService); - this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + this.userDecryptionOptionsService = new UserDecryptionOptionsService( + this.singleUserStateProvider, + ); this.organizationService = new DefaultOrganizationService(this.stateProvider); this.policyService = new DefaultPolicyService(this.stateProvider, this.organizationService); @@ -859,8 +861,6 @@ export default class MainBackground { this.stateProvider, ); - this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); - this.devicesApiService = new DevicesApiServiceImplementation(this.apiService); this.deviceTrustService = new DeviceTrustService( this.keyGenerationService, @@ -876,6 +876,7 @@ export default class MainBackground { this.userDecryptionOptionsService, this.logService, this.configService, + this.accountService, ); this.devicesService = new DevicesServiceImplementation( diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index eebf0a08a22..c462319dc2e 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -36,6 +36,7 @@ import { LoginEmailService, SsoUrlService, LogoutService, + UserDecryptionOptionsServiceAbstraction, } from "@bitwarden/auth/common"; import { ExtensionNewDeviceVerificationComponentService } from "@bitwarden/browser/auth/services/new-device-verification/extension-new-device-verification-component.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -607,7 +608,12 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: Fido2UserVerificationService, useClass: Fido2UserVerificationService, - deps: [PasswordRepromptService, UserVerificationService, DialogService], + deps: [ + PasswordRepromptService, + UserDecryptionOptionsServiceAbstraction, + DialogService, + AccountServiceAbstraction, + ], }), safeProvider({ provide: AnimationControlService, diff --git a/apps/browser/src/vault/services/fido2-user-verification.service.spec.ts b/apps/browser/src/vault/services/fido2-user-verification.service.spec.ts index 97a22bb2cf3..e55e3091244 100644 --- a/apps/browser/src/vault/services/fido2-user-verification.service.spec.ts +++ b/apps/browser/src/vault/services/fido2-user-verification.service.spec.ts @@ -1,11 +1,15 @@ import { MockProxy, mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; +import { newGuid } from "@bitwarden/guid"; import { PasswordRepromptService } from "@bitwarden/vault"; // FIXME (PM-22628): Popup imports are forbidden in background @@ -31,21 +35,24 @@ describe("Fido2UserVerificationService", () => { let fido2UserVerificationService: Fido2UserVerificationService; let passwordRepromptService: MockProxy<PasswordRepromptService>; - let userVerificationService: MockProxy<UserVerificationService>; + let userDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>; let dialogService: MockProxy<DialogService>; + let accountService: FakeAccountService; let cipher: CipherView; beforeEach(() => { passwordRepromptService = mock<PasswordRepromptService>(); - userVerificationService = mock<UserVerificationService>(); + userDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>(); dialogService = mock<DialogService>(); + accountService = mockAccountServiceWith(newGuid() as UserId); cipher = createCipherView(); fido2UserVerificationService = new Fido2UserVerificationService( passwordRepromptService, - userVerificationService, + userDecryptionOptionsService, dialogService, + accountService, ); (UserVerificationDialogComponent.open as jest.Mock).mockResolvedValue({ @@ -67,7 +74,7 @@ describe("Fido2UserVerificationService", () => { it("should call master password reprompt dialog if user is redirected from lock screen, has master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(true); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true)); passwordRepromptService.showPasswordPrompt.mockResolvedValue(true); const result = await fido2UserVerificationService.handleUserVerification( @@ -82,7 +89,7 @@ describe("Fido2UserVerificationService", () => { it("should call user verification dialog if user is redirected from lock screen, does not have a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); const result = await fido2UserVerificationService.handleUserVerification( true, @@ -98,7 +105,7 @@ describe("Fido2UserVerificationService", () => { it("should call user verification dialog if user is not redirected from lock screen, does not have a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); const result = await fido2UserVerificationService.handleUserVerification( true, @@ -114,7 +121,7 @@ describe("Fido2UserVerificationService", () => { it("should call master password reprompt dialog if user is not redirected from lock screen, has a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); passwordRepromptService.showPasswordPrompt.mockResolvedValue(true); const result = await fido2UserVerificationService.handleUserVerification( @@ -176,7 +183,7 @@ describe("Fido2UserVerificationService", () => { it("should call master password reprompt dialog if user is redirected from lock screen, has master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(true); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true)); passwordRepromptService.showPasswordPrompt.mockResolvedValue(true); const result = await fido2UserVerificationService.handleUserVerification( @@ -191,7 +198,7 @@ describe("Fido2UserVerificationService", () => { it("should call user verification dialog if user is redirected from lock screen, does not have a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); const result = await fido2UserVerificationService.handleUserVerification( false, @@ -207,7 +214,7 @@ describe("Fido2UserVerificationService", () => { it("should call user verification dialog if user is not redirected from lock screen, does not have a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); const result = await fido2UserVerificationService.handleUserVerification( false, @@ -223,7 +230,7 @@ describe("Fido2UserVerificationService", () => { it("should call master password reprompt dialog if user is not redirected from lock screen, has a master password and master password reprompt is required", async () => { cipher.reprompt = CipherRepromptType.Password; - userVerificationService.hasMasterPassword.mockResolvedValue(false); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); passwordRepromptService.showPasswordPrompt.mockResolvedValue(true); const result = await fido2UserVerificationService.handleUserVerification( diff --git a/apps/browser/src/vault/services/fido2-user-verification.service.ts b/apps/browser/src/vault/services/fido2-user-verification.service.ts index 9bf9be70fc8..db3951d44d9 100644 --- a/apps/browser/src/vault/services/fido2-user-verification.service.ts +++ b/apps/browser/src/vault/services/fido2-user-verification.service.ts @@ -3,7 +3,8 @@ import { firstValueFrom } from "rxjs"; import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -15,8 +16,9 @@ import { SetPinComponent } from "../../auth/popup/components/set-pin.component"; export class Fido2UserVerificationService { constructor( private passwordRepromptService: PasswordRepromptService, - private userVerificationService: UserVerificationService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private dialogService: DialogService, + private accountService: AccountService, ) {} /** @@ -78,7 +80,15 @@ export class Fido2UserVerificationService { } private async handleMasterPasswordReprompt(): Promise<boolean> { - const hasMasterPassword = await this.userVerificationService.hasMasterPassword(); + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + + if (!activeAccount?.id) { + return false; + } + + const hasMasterPassword = await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPasswordById$(activeAccount.id), + ); // TDE users have no master password, so we need to use the UserVerification prompt return hasMasterPassword diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 365205a1329..122dd6ea052 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -512,7 +512,9 @@ export class ServiceContainer { ")"; this.biometricStateService = new DefaultBiometricStateService(this.stateProvider); - this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider); + this.userDecryptionOptionsService = new UserDecryptionOptionsService( + this.singleUserStateProvider, + ); this.ssoUrlService = new SsoUrlService(); this.organizationService = new DefaultOrganizationService(this.stateProvider); @@ -702,6 +704,7 @@ export class ServiceContainer { this.userDecryptionOptionsService, this.logService, this.configService, + this.accountService, ); this.loginStrategyService = new LoginStrategyService( diff --git a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts index 53a1c7dbd4c..717af25a1dc 100644 --- a/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts +++ b/apps/desktop/src/app/services/set-initial-password/desktop-set-initial-password.service.spec.ts @@ -119,7 +119,9 @@ describe("DesktopSetInitialPasswordService", () => { userDecryptionOptions = new UserDecryptionOptions({ hasMasterPassword: true }); userDecryptionOptionsSubject = new BehaviorSubject(userDecryptionOptions); - userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject; + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + userDecryptionOptionsSubject, + ); setPasswordRequest = new SetPasswordRequest( credentials.newServerMasterKeyHash, diff --git a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts index 70f7686a2cd..647c9ae83d9 100644 --- a/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts +++ b/apps/web/src/app/auth/core/services/password-management/set-initial-password/web-set-initial-password.service.spec.ts @@ -123,7 +123,9 @@ describe("WebSetInitialPasswordService", () => { userDecryptionOptions = new UserDecryptionOptions({ hasMasterPassword: true }); userDecryptionOptionsSubject = new BehaviorSubject(userDecryptionOptions); - userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject; + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + userDecryptionOptionsSubject, + ); setPasswordRequest = new SetPasswordRequest( credentials.newServerMasterKeyHash, diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index 8bae8cd2c1f..3e618b89dbe 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -1,11 +1,10 @@ import { Component, OnInit, OnDestroy } from "@angular/core"; -import { firstValueFrom, from, lastValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; +import { firstValueFrom, lastValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; import { HeaderModule } from "../../../layouts/header/header.module"; @@ -42,8 +41,7 @@ export class AccountComponent implements OnInit, OnDestroy { constructor( private accountService: AccountService, private dialogService: DialogService, - private userVerificationService: UserVerificationService, - private configService: ConfigService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private organizationService: OrganizationService, ) {} @@ -56,7 +54,7 @@ export class AccountComponent implements OnInit, OnDestroy { map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)), ); - const hasMasterPassword$ = from(this.userVerificationService.hasMasterPassword()); + const hasMasterPassword$ = this.userDecryptionOptionsService.hasMasterPasswordById$(userId); this.showChangeEmail$ = hasMasterPassword$; diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts index 0e37c856935..ee283d26415 100644 --- a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts @@ -5,6 +5,8 @@ import { firstValueFrom } from "rxjs"; import { ChangePasswordComponent } from "@bitwarden/angular/auth/password-management/change-password"; import { InputPasswordFlow } from "@bitwarden/auth/angular"; import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CalloutModule } from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -24,12 +26,15 @@ export class PasswordSettingsComponent implements OnInit { constructor( private router: Router, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private accountService: AccountService, ) {} async ngOnInit() { + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const userHasMasterPassword = await firstValueFrom( - this.userDecryptionOptionsService.hasMasterPassword$, + this.userDecryptionOptionsService.hasMasterPasswordById$(userId), ); + if (!userHasMasterPassword) { await this.router.navigate(["/settings/security/two-factor"]); return; diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 27a555ff343..b62828a2783 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -1,11 +1,10 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DialogService } from "@bitwarden/components"; import { ChangeKdfModule } from "../../../key-management/change-kdf/change-kdf.module"; @@ -23,20 +22,28 @@ export class SecurityKeysComponent implements OnInit { showChangeKdf = true; constructor( - private userVerificationService: UserVerificationService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private accountService: AccountService, private apiService: ApiService, private dialogService: DialogService, ) {} async ngOnInit() { - this.showChangeKdf = await this.userVerificationService.hasMasterPassword(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.showChangeKdf = await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPasswordById$(userId), + ); } async viewUserApiKey() { const entityId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + + if (!entityId) { + throw new Error("Active account not found"); + } + await ApiKeyComponent.open(this.dialogService, { data: { keyType: "user", @@ -55,6 +62,11 @@ export class SecurityKeysComponent implements OnInit { const entityId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + + if (!entityId) { + throw new Error("Active account not found"); + } + await ApiKeyComponent.open(this.dialogService, { data: { keyType: "user", diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 629de32efc4..85bc29fac63 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -1,7 +1,9 @@ import { Component, OnInit } from "@angular/core"; -import { Observable } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -20,7 +22,8 @@ export class SecurityComponent implements OnInit { consolidatedSessionTimeoutComponent$: Observable<boolean>; constructor( - private userVerificationService: UserVerificationService, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + private accountService: AccountService, private configService: ConfigService, ) { this.consolidatedSessionTimeoutComponent$ = this.configService.getFeatureFlag$( @@ -29,6 +32,9 @@ export class SecurityComponent implements OnInit { } async ngOnInit() { - this.showChangePassword = await this.userVerificationService.hasMasterPassword(); + const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + this.showChangePassword = userId + ? await firstValueFrom(this.userDecryptionOptionsService.hasMasterPasswordById$(userId)) + : false; } } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 3b707f2d78c..37b881406e3 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -95,7 +95,10 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { combineLatest([ this.organization$, resetPasswordPolicies$, - this.userDecryptionOptionsService.userDecryptionOptions$, + this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.userDecryptionOptionsService.userDecryptionOptionsById$(userId)), + ), managingOrg$, ]) .pipe(takeUntil(this.destroy$)) diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts index dcd4fd93cba..df5220b5255 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.implementation.ts @@ -198,10 +198,13 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi userId: UserId, ) { const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, + this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), ); userDecryptionOpts.hasMasterPassword = true; - await this.userDecryptionOptionsService.setUserDecryptionOptions(userDecryptionOpts); + await this.userDecryptionOptionsService.setUserDecryptionOptionsById( + userId, + userDecryptionOpts, + ); await this.kdfConfigService.setKdfConfig(userId, kdfConfig); await this.masterPasswordService.setMasterKey(masterKey, userId); await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId); diff --git a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts index 7a1dfc91e67..8b95090e776 100644 --- a/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts +++ b/libs/angular/src/auth/password-management/set-initial-password/default-set-initial-password.service.spec.ts @@ -149,7 +149,9 @@ describe("DefaultSetInitialPasswordService", () => { userDecryptionOptions = new UserDecryptionOptions({ hasMasterPassword: true }); userDecryptionOptionsSubject = new BehaviorSubject(userDecryptionOptions); - userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject; + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + userDecryptionOptionsSubject, + ); setPasswordRequest = new SetPasswordRequest( credentials.newServerMasterKeyHash, @@ -362,7 +364,8 @@ describe("DefaultSetInitialPasswordService", () => { // Assert expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); - expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith( + expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith( + userId, userDecryptionOptions, ); expect(kdfConfigService.setKdfConfig).toHaveBeenCalledWith(userId, credentials.kdfConfig); @@ -560,7 +563,8 @@ describe("DefaultSetInitialPasswordService", () => { // Assert expect(masterPasswordApiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); - expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith( + expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith( + userId, userDecryptionOptions, ); expect(kdfConfigService.setKdfConfig).toHaveBeenCalledWith(userId, credentials.kdfConfig); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index ff5caff540c..bcb601a993c 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -684,7 +684,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: InternalUserDecryptionOptionsServiceAbstraction, useClass: UserDecryptionOptionsService, - deps: [StateProvider], + deps: [SingleUserStateProvider], }), safeProvider({ provide: UserDecryptionOptionsServiceAbstraction, @@ -1292,6 +1292,7 @@ const safeProviders: SafeProvider[] = [ UserDecryptionOptionsServiceAbstraction, LogService, ConfigService, + AccountServiceAbstraction, ], }), safeProvider({ diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 26293285008..fb07069998b 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -135,7 +135,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { try { const userDecryptionOptions = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, + this.userDecryptionOptionsService.userDecryptionOptionsById$(this.activeAccountId), ); if ( diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index 0b6bb1159f4..bf618ba39f4 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -460,7 +460,7 @@ export class SsoComponent implements OnInit { // must come after 2fa check since user decryption options aren't available if 2fa is required const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, + this.userDecryptionOptionsService.userDecryptionOptionsById$(authResult.userId), ); const tdeEnabled = userDecryptionOpts.trustedDeviceOption diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index af9fb03e01e..8c12060168b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -176,7 +176,9 @@ describe("TwoFactorAuthComponent", () => { selectedUserDecryptionOptions = new BehaviorSubject<UserDecryptionOptions>( mockUserDecryptionOpts.withMasterPassword, ); - mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; + mockUserDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + selectedUserDecryptionOptions, + ); TestBed.configureTestingModule({ declarations: [TestTwoFactorComponent], diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 8e10539823d..ca19d3652bb 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -473,7 +473,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, + this.userDecryptionOptionsService.userDecryptionOptionsById$(authResult.userId), ); const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption); diff --git a/libs/auth/src/common/abstractions/user-decryption-options.service.abstraction.ts b/libs/auth/src/common/abstractions/user-decryption-options.service.abstraction.ts index e46fb09cff6..443e43b3151 100644 --- a/libs/auth/src/common/abstractions/user-decryption-options.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/user-decryption-options.service.abstraction.ts @@ -1,34 +1,45 @@ import { Observable } from "rxjs"; +import { UserId } from "@bitwarden/common/types/guid"; + import { UserDecryptionOptions } from "../models"; +/** + * Public service for reading user decryption options. + * For use in components and services that need to evaluate user decryption settings. + */ export abstract class UserDecryptionOptionsServiceAbstraction { /** - * Returns what decryption options are available for the current user. - * @remark This is sent from the server on authentication. + * Returns the user decryption options for the given user id. + * Will only emit when options are set (does not emit null/undefined + * for an unpopulated state), and should not be called in an unauthenticated context. + * @param userId The user id to check. */ - abstract userDecryptionOptions$: Observable<UserDecryptionOptions>; + abstract userDecryptionOptionsById$(userId: UserId): Observable<UserDecryptionOptions>; /** * Uses user decryption options to determine if current user has a master password. * @remark This is sent from the server, and does not indicate if the master password * was used to login and/or if a master key is saved locally. */ - abstract hasMasterPassword$: Observable<boolean>; - - /** - * Returns the user decryption options for the given user id. - * @param userId The user id to check. - */ - abstract userDecryptionOptionsById$(userId: string): Observable<UserDecryptionOptions>; + abstract hasMasterPasswordById$(userId: UserId): Observable<boolean>; } +/** + * Internal service for managing user decryption options. + * For use only in authentication flows that need to update decryption options + * (e.g., login strategies). Extends consumer methods from {@link UserDecryptionOptionsServiceAbstraction}. + * @remarks Most consumers should use UserDecryptionOptionsServiceAbstraction instead. + */ export abstract class InternalUserDecryptionOptionsServiceAbstraction extends UserDecryptionOptionsServiceAbstraction { /** - * Sets the current decryption options for the user, contains the current configuration + * Sets the current decryption options for the user. Contains the current configuration * of the users account related to how they can decrypt their vault. * @remark Intended to be used when user decryption options are received from server, does * not update the server. Consider syncing instead of updating locally. * @param userDecryptionOptions Current user decryption options received from server. */ - abstract setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void>; + abstract setUserDecryptionOptionsById( + userId: UserId, + userDecryptionOptions: UserDecryptionOptions, + ): Promise<void>; } diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index e9eed27d5a1..38d62cfdd83 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -257,7 +257,8 @@ describe("LoginStrategy", () => { expect(environmentService.seedUserEnvironment).toHaveBeenCalled(); - expect(userDecryptionOptionsService.setUserDecryptionOptions).toHaveBeenCalledWith( + expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith( + userId, UserDecryptionOptions.fromResponse(idTokenResponse), ); expect(masterPasswordService.mock.setMasterPasswordUnlockData).toHaveBeenCalledWith( diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 35f13246593..b8e4ee9e822 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -195,7 +195,8 @@ export abstract class LoginStrategy { // We must set user decryption options before retrieving vault timeout settings // as the user decryption options help determine the available timeout actions. - await this.userDecryptionOptionsService.setUserDecryptionOptions( + await this.userDecryptionOptionsService.setUserDecryptionOptionsById( + userId, UserDecryptionOptions.fromResponse(tokenResponse), ); diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index 03de5f36c2d..acbb680ae6d 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -134,7 +134,9 @@ describe("SsoLoginStrategy", () => { ); const userDecryptionOptions = new UserDecryptionOptions(); - userDecryptionOptionsService.userDecryptionOptions$ = of(userDecryptionOptions); + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + of(userDecryptionOptions), + ); ssoLoginStrategy = new SsoLoginStrategy( {} as SsoLoginStrategyData, diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index ec7914b087e..d806f6d733e 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -393,7 +393,7 @@ export class SsoLoginStrategy extends LoginStrategy { // Check for TDE-related conditions const userDecryptionOptions = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, + this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), ); if (!userDecryptionOptions) { diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts index c2fafc1a2f6..efb00b9aa63 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.spec.ts @@ -1,12 +1,8 @@ import { firstValueFrom } from "rxjs"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { - FakeAccountService, - FakeStateProvider, - mockAccountServiceWith, -} from "@bitwarden/common/spec"; +import { FakeSingleUserStateProvider } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { newGuid } from "@bitwarden/guid"; import { UserDecryptionOptions } from "../../models/domain/user-decryption-options"; @@ -17,15 +13,10 @@ import { describe("UserDecryptionOptionsService", () => { let sut: UserDecryptionOptionsService; - - const fakeUserId = Utils.newGuid() as UserId; - let fakeAccountService: FakeAccountService; - let fakeStateProvider: FakeStateProvider; + let fakeStateProvider: FakeSingleUserStateProvider; beforeEach(() => { - fakeAccountService = mockAccountServiceWith(fakeUserId); - fakeStateProvider = new FakeStateProvider(fakeAccountService); - + fakeStateProvider = new FakeSingleUserStateProvider(); sut = new UserDecryptionOptionsService(fakeStateProvider); }); @@ -42,55 +33,71 @@ describe("UserDecryptionOptionsService", () => { }, }; - describe("userDecryptionOptions$", () => { - it("should return the active user's decryption options", async () => { - await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions); + describe("userDecryptionOptionsById$", () => { + it("should return user decryption options for a specific user", async () => { + const userId = newGuid() as UserId; - const result = await firstValueFrom(sut.userDecryptionOptions$); + fakeStateProvider.getFake(userId, USER_DECRYPTION_OPTIONS).nextState(userDecryptionOptions); + + const result = await firstValueFrom(sut.userDecryptionOptionsById$(userId)); expect(result).toEqual(userDecryptionOptions); }); }); - describe("hasMasterPassword$", () => { - it("should return the hasMasterPassword property of the active user's decryption options", async () => { - await fakeStateProvider.setUserState(USER_DECRYPTION_OPTIONS, userDecryptionOptions); + describe("hasMasterPasswordById$", () => { + it("should return true when user has a master password", async () => { + const userId = newGuid() as UserId; - const result = await firstValueFrom(sut.hasMasterPassword$); + fakeStateProvider.getFake(userId, USER_DECRYPTION_OPTIONS).nextState(userDecryptionOptions); + + const result = await firstValueFrom(sut.hasMasterPasswordById$(userId)); expect(result).toBe(true); }); - }); - describe("userDecryptionOptionsById$", () => { - it("should return the user decryption options for the given user", async () => { - const givenUser = Utils.newGuid() as UserId; - await fakeAccountService.addAccount(givenUser, { - name: "Test User 1", - email: "test1@email.com", - emailVerified: false, - }); - await fakeStateProvider.setUserState( - USER_DECRYPTION_OPTIONS, - userDecryptionOptions, - givenUser, - ); + it("should return false when user does not have a master password", async () => { + const userId = newGuid() as UserId; + const optionsWithoutMasterPassword = { + ...userDecryptionOptions, + hasMasterPassword: false, + }; - const result = await firstValueFrom(sut.userDecryptionOptionsById$(givenUser)); + fakeStateProvider + .getFake(userId, USER_DECRYPTION_OPTIONS) + .nextState(optionsWithoutMasterPassword); - expect(result).toEqual(userDecryptionOptions); + const result = await firstValueFrom(sut.hasMasterPasswordById$(userId)); + + expect(result).toBe(false); }); }); - describe("setUserDecryptionOptions", () => { - it("should set the active user's decryption options", async () => { - await sut.setUserDecryptionOptions(userDecryptionOptions); + describe("setUserDecryptionOptionsById", () => { + it("should set user decryption options for a specific user", async () => { + const userId = newGuid() as UserId; - const result = await firstValueFrom( - fakeStateProvider.getActive(USER_DECRYPTION_OPTIONS).state$, - ); + await sut.setUserDecryptionOptionsById(userId, userDecryptionOptions); + + const fakeState = fakeStateProvider.getFake(userId, USER_DECRYPTION_OPTIONS); + const result = await firstValueFrom(fakeState.state$); expect(result).toEqual(userDecryptionOptions); }); + + it("should overwrite existing user decryption options", async () => { + const userId = newGuid() as UserId; + const initialOptions = { ...userDecryptionOptions, hasMasterPassword: false }; + const updatedOptions = { ...userDecryptionOptions, hasMasterPassword: true }; + + const fakeState = fakeStateProvider.getFake(userId, USER_DECRYPTION_OPTIONS); + fakeState.nextState(initialOptions); + + await sut.setUserDecryptionOptionsById(userId, updatedOptions); + + const result = await firstValueFrom(fakeState.state$); + + expect(result).toEqual(updatedOptions); + }); }); }); diff --git a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts index 7c44a6f1682..a0075d1987b 100644 --- a/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts +++ b/libs/auth/src/common/services/user-decryption-options/user-decryption-options.service.ts @@ -1,16 +1,11 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Observable, map } from "rxjs"; +import { Observable, filter, map } from "rxjs"; import { - ActiveUserState, - StateProvider, + SingleUserStateProvider, USER_DECRYPTION_OPTIONS_DISK, UserKeyDefinition, } from "@bitwarden/common/platform/state"; -// FIXME: remove `src` and fix import -// eslint-disable-next-line no-restricted-imports -import { UserId } from "@bitwarden/common/src/types/guid"; +import { UserId } from "@bitwarden/common/types/guid"; import { InternalUserDecryptionOptionsServiceAbstraction } from "../../abstractions/user-decryption-options.service.abstraction"; import { UserDecryptionOptions } from "../../models"; @@ -27,25 +22,26 @@ export const USER_DECRYPTION_OPTIONS = new UserKeyDefinition<UserDecryptionOptio export class UserDecryptionOptionsService implements InternalUserDecryptionOptionsServiceAbstraction { - private userDecryptionOptionsState: ActiveUserState<UserDecryptionOptions>; + constructor(private singleUserStateProvider: SingleUserStateProvider) {} - userDecryptionOptions$: Observable<UserDecryptionOptions>; - hasMasterPassword$: Observable<boolean>; + userDecryptionOptionsById$(userId: UserId): Observable<UserDecryptionOptions> { + return this.singleUserStateProvider + .get(userId, USER_DECRYPTION_OPTIONS) + .state$.pipe(filter((options): options is UserDecryptionOptions => options != null)); + } - constructor(private stateProvider: StateProvider) { - this.userDecryptionOptionsState = this.stateProvider.getActive(USER_DECRYPTION_OPTIONS); - - this.userDecryptionOptions$ = this.userDecryptionOptionsState.state$; - this.hasMasterPassword$ = this.userDecryptionOptions$.pipe( - map((options) => options?.hasMasterPassword ?? false), + hasMasterPasswordById$(userId: UserId): Observable<boolean> { + return this.userDecryptionOptionsById$(userId).pipe( + map((options) => options.hasMasterPassword ?? false), ); } - userDecryptionOptionsById$(userId: UserId): Observable<UserDecryptionOptions> { - return this.stateProvider.getUser(userId, USER_DECRYPTION_OPTIONS).state$; - } - - async setUserDecryptionOptions(userDecryptionOptions: UserDecryptionOptions): Promise<void> { - await this.userDecryptionOptionsState.update((_) => userDecryptionOptions); + async setUserDecryptionOptionsById( + userId: UserId, + userDecryptionOptions: UserDecryptionOptions, + ): Promise<void> { + await this.singleUserStateProvider + .get(userId, USER_DECRYPTION_OPTIONS) + .update((_) => userDecryptionOptions); } } diff --git a/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts b/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts index d9749d9467c..b9bc9108e52 100644 --- a/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts +++ b/libs/common/src/auth/abstractions/user-verification/user-verification.service.abstraction.ts @@ -48,6 +48,9 @@ export abstract class UserVerificationService { * @param userId The user id to check. If not provided, the current user is used * @returns True if the user has a master password * @deprecated Use UserDecryptionOptionsService.hasMasterPassword$ instead + * @remark To facilitate deprecation, many call sites were removed as part of PM-26413. + * Those remaining are blocked by currently-disallowed imports of auth/common. + * PM-27009 has been filed to track completion of this deprecation. */ abstract hasMasterPassword(userId?: string): Promise<boolean>; /** diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts index e7fbc002af8..e570c0f4a43 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.spec.ts @@ -3,10 +3,7 @@ import { of } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports -import { - UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { @@ -146,11 +143,7 @@ describe("UserVerificationService", () => { describe("server verification type", () => { it("correctly returns master password availability", async () => { - userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( - of({ - hasMasterPassword: true, - } as UserDecryptionOptions), - ); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(true)); const result = await sut.getAvailableVerificationOptions("server"); @@ -168,11 +161,7 @@ describe("UserVerificationService", () => { }); it("correctly returns OTP availability", async () => { - userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( - of({ - hasMasterPassword: false, - } as UserDecryptionOptions), - ); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(false)); const result = await sut.getAvailableVerificationOptions("server"); @@ -526,11 +515,7 @@ describe("UserVerificationService", () => { // Helpers function setMasterPasswordAvailability(hasMasterPassword: boolean) { - userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( - of({ - hasMasterPassword: hasMasterPassword, - } as UserDecryptionOptions), - ); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue(of(hasMasterPassword)); masterPasswordService.masterKeyHash$.mockReturnValue( of(hasMasterPassword ? "masterKeyHash" : null), ); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 7a537b883e3..7d93120148b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -258,16 +258,19 @@ export class UserVerificationService implements UserVerificationServiceAbstracti } async hasMasterPassword(userId?: string): Promise<boolean> { - if (userId) { - const decryptionOptions = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), - ); + const resolvedUserId = userId ?? (await firstValueFrom(this.accountService.activeAccount$))?.id; - if (decryptionOptions?.hasMasterPassword != undefined) { - return decryptionOptions.hasMasterPassword; - } + if (!resolvedUserId) { + return false; } - return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$); + + // Ideally, this method would accept a UserId over string. To avoid scope creep in PM-26413, we are + // doing the cast here. Future work should be done to make this type-safe, and should be considered + // as part of PM-27009. + + return await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPasswordById$(resolvedUserId as UserId), + ); } async hasMasterPasswordAndMasterKeyHash(userId?: string): Promise<boolean> { diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index aa14c7f0c4f..59bd7bc11f2 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map, Observable, Subject } from "rxjs"; +import { firstValueFrom, map, Observable, Subject, switchMap } from "rxjs"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports @@ -9,6 +9,7 @@ import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common" // eslint-disable-next-line no-restricted-imports import { KeyService } from "@bitwarden/key-management"; +import { AccountService } from "../../../auth/abstractions/account.service"; import { DeviceResponse } from "../../../auth/abstractions/devices/responses/device.response"; import { DevicesApiServiceAbstraction } from "../../../auth/abstractions/devices-api.service.abstraction"; import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request"; @@ -87,10 +88,18 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private logService: LogService, private configService: ConfigService, + private accountService: AccountService, ) { - this.supportsDeviceTrust$ = this.userDecryptionOptionsService.userDecryptionOptions$.pipe( - map((options) => { - return options?.trustedDeviceOption != null; + this.supportsDeviceTrust$ = this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (account == null) { + return [false]; + } + return this.userDecryptionOptionsService.userDecryptionOptionsById$(account.id).pipe( + map((options) => { + return options?.trustedDeviceOption != null; + }), + ); }), ); } diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index e735295f42b..024a21766ee 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -914,7 +914,7 @@ describe("deviceTrustService", () => { platformUtilsService.supportsSecureStorage.mockReturnValue(supportsSecureStorage); decryptionOptions.next({} as any); - userDecryptionOptionsService.userDecryptionOptions$ = decryptionOptions; + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue(decryptionOptions); return new DeviceTrustService( keyGenerationService, @@ -930,6 +930,7 @@ describe("deviceTrustService", () => { userDecryptionOptionsService, logService, configService, + accountService, ); } }); diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts index f3fec6d849a..ba58fa80922 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.spec.ts @@ -53,9 +53,11 @@ describe("VaultTimeoutSettingsService", () => { policyService = mock<PolicyService>(); userDecryptionOptionsSubject = new BehaviorSubject(null); - userDecryptionOptionsService.userDecryptionOptions$ = userDecryptionOptionsSubject; - userDecryptionOptionsService.hasMasterPassword$ = userDecryptionOptionsSubject.pipe( - map((options) => options?.hasMasterPassword ?? false), + userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( + userDecryptionOptionsSubject, + ); + userDecryptionOptionsService.hasMasterPasswordById$.mockReturnValue( + userDecryptionOptionsSubject.pipe(map((options) => options?.hasMasterPassword ?? false)), ); userDecryptionOptionsService.userDecryptionOptionsById$.mockReturnValue( userDecryptionOptionsSubject, @@ -127,6 +129,23 @@ describe("VaultTimeoutSettingsService", () => { expect(result).not.toContain(VaultTimeoutAction.Lock); }); + + it("should return only LogOut when userId is not provided and there is no active account", async () => { + // Set up accountService to return null for activeAccount + accountService.activeAccount$ = of(null); + pinStateService.isPinSet.mockResolvedValue(false); + biometricStateService.biometricUnlockEnabled$ = of(false); + + // Call availableVaultTimeoutActions$ which internally calls userHasMasterPassword without a userId + const result = await firstValueFrom( + vaultTimeoutSettingsService.availableVaultTimeoutActions$(), + ); + + // Since there's no active account, userHasMasterPassword returns false, + // meaning no master password is available, so Lock should not be available + expect(result).toEqual([VaultTimeoutAction.LogOut]); + expect(result).not.toContain(VaultTimeoutAction.Lock); + }); }); describe("canLock", () => { diff --git a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts index 4ca23bb24bf..00e53596de4 100644 --- a/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts +++ b/libs/common/src/key-management/vault-timeout/services/vault-timeout-settings.service.ts @@ -290,14 +290,19 @@ export class VaultTimeoutSettingsService implements VaultTimeoutSettingsServiceA } private async userHasMasterPassword(userId: string): Promise<boolean> { + let resolvedUserId: UserId; if (userId) { - const decryptionOptions = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptionsById$(userId), - ); - - return !!decryptionOptions?.hasMasterPassword; + resolvedUserId = userId as UserId; } else { - return await firstValueFrom(this.userDecryptionOptionsService.hasMasterPassword$); + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + if (!activeAccount) { + return false; // No account, can't have master password + } + resolvedUserId = activeAccount.id; } + + return await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPasswordById$(resolvedUserId), + ); } } From 17ae78ea8315e5b266d7ff9af393c61848859825 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:23:01 -0600 Subject: [PATCH 236/249] chore: fix feature flag name, refs PM-27766 (#17660) --- libs/common/src/enums/feature-flag.enum.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 17d5f4e9df5..56a25cb213c 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,7 +13,7 @@ export enum FeatureFlag { /* Admin Console Team */ CreateDefaultLocation = "pm-19467-create-default-location", AutoConfirm = "pm-19934-auto-confirm-organization-users", - BlockClaimedDomainAccountCreation = "block-claimed-domain-account-creation", + BlockClaimedDomainAccountCreation = "pm-28297-block-uninvited-claimed-domain-registration", /* Auth */ PM23801_PrefetchPasswordPrelogin = "pm-23801-prefetch-password-prelogin", From 441783627b9af11a0ca339dcc7aa789787b3c468 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:28:34 -0600 Subject: [PATCH 237/249] [PM-26359] Archive Upgrade - Browser (#16904) * add archive upgrade flow to more options menu * add reprompt for archiving a cipher * add premium badge for archive in settings * update showArchive to only look at the feature flag * add premium badge for browser settings * add event to prompt for premium * formatting * update test --- apps/browser/src/_locales/en/messages.json | 3 ++ .../item-more-options.component.html | 24 +++++++++-- .../item-more-options.component.spec.ts | 5 ++- .../item-more-options.component.ts | 40 ++++++++++++------- .../settings/vault-settings-v2.component.html | 28 +++++++++---- .../settings/vault-settings-v2.component.ts | 21 +++++++--- 6 files changed, 90 insertions(+), 31 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 14915175da1..6009bcba1bd 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -585,6 +585,9 @@ "archiveItemConfirmDesc": { "message": "Archived items are excluded from general search results and autofill suggestions. Are you sure you want to archive this item?" }, + "upgradeToUseArchive": { + "message": "A premium membership is required to use Archive." + }, "edit": { "message": "Edit" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html index b6a7002139c..5c5171ac81d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.html @@ -51,10 +51,26 @@ {{ "assignToCollections" | i18n }} </a> </ng-container> - @if (canArchive$ | async) { - <button type="button" bitMenuItem (click)="archive()"> - {{ "archiveVerb" | i18n }} - </button> + @if (showArchive$ | async) { + @if (canArchive$ | async) { + <button type="button" bitMenuItem (click)="archive()"> + {{ "archiveVerb" | i18n }} + </button> + } @else { + <button + type="button" + bitMenuItem + (click)="badge.promptForPremium($event)" + [attr.aria-label]="'upgradeToUseArchive' | i18n" + > + <div class="tw-flex tw-flex-nowrap tw-items-center tw-gap-2"> + {{ "archiveVerb" | i18n }} + <div aria-hidden> + <app-premium-badge #badge></app-premium-badge> + </div> + </div> + </button> + } } @if (canDelete$ | async) { <button type="button" bitMenuItem (click)="delete()"> diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts index 7b71c2b470f..577b7d96771 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.spec.ts @@ -106,7 +106,10 @@ describe("ItemMoreOptionsComponent", () => { }, { provide: CollectionService, useValue: { decryptedCollections$: () => of([]) } }, { provide: RestrictedItemTypesService, useValue: { restricted$: of([]) } }, - { provide: CipherArchiveService, useValue: { userCanArchive$: () => of(true) } }, + { + provide: CipherArchiveService, + useValue: { userCanArchive$: () => of(true), hasArchiveFlagEnabled$: () => of(true) }, + }, { provide: ToastService, useValue: { showToast: () => {} } }, { provide: Router, useValue: { navigate: () => Promise.resolve(true) } }, { provide: PasswordRepromptService, useValue: passwordRepromptService }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts index b498e7cd9a5..4dfaf7bc66f 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/item-more-options/item-more-options.component.ts @@ -1,10 +1,11 @@ import { CommonModule } from "@angular/common"; import { booleanAttribute, Component, Input } from "@angular/core"; import { Router, RouterModule } from "@angular/router"; -import { BehaviorSubject, combineLatest, firstValueFrom, map, switchMap } from "rxjs"; +import { BehaviorSubject, combineLatest, firstValueFrom, map, Observable, switchMap } from "rxjs"; import { filter } from "rxjs/operators"; import { CollectionService } from "@bitwarden/admin-console/common"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -17,6 +18,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { CipherRepromptType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { RestrictedItemTypesService } from "@bitwarden/common/vault/services/restricted-item-types.service"; @@ -33,6 +35,7 @@ import { } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; +import { BrowserPremiumUpgradePromptService } from "../../../services/browser-premium-upgrade-prompt.service"; import { VaultPopupAutofillService } from "../../../services/vault-popup-autofill.service"; import { VaultPopupItemsService } from "../../../services/vault-popup-items.service"; import { AddEditQueryParams } from "../add-edit/add-edit-v2.component"; @@ -46,7 +49,18 @@ import { @Component({ selector: "app-item-more-options", templateUrl: "./item-more-options.component.html", - imports: [ItemModule, IconButtonModule, MenuModule, CommonModule, JslibModule, RouterModule], + imports: [ + ItemModule, + IconButtonModule, + MenuModule, + CommonModule, + JslibModule, + RouterModule, + PremiumBadgeComponent, + ], + providers: [ + { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService }, + ], }) export class ItemMoreOptionsComponent { private _cipher$ = new BehaviorSubject<CipherViewLike>({} as CipherViewLike); @@ -127,18 +141,11 @@ export class ItemMoreOptionsComponent { }), ); - /** Observable Boolean checking if item can show Archive menu option */ - protected canArchive$ = combineLatest([ - this._cipher$, - this.accountService.activeAccount$.pipe( - getUserId, - switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)), - ), - ]).pipe( - filter(([cipher, userId]) => cipher != null && userId != null), - map(([cipher, canArchive]) => { - return canArchive && !CipherViewLikeUtils.isArchived(cipher) && cipher.organizationId == null; - }), + protected showArchive$: Observable<boolean> = this.cipherArchiveService.hasArchiveFlagEnabled$(); + + protected canArchive$: Observable<boolean> = this.accountService.activeAccount$.pipe( + getUserId, + switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)), ); protected canDelete$ = this._cipher$.pipe( @@ -377,6 +384,11 @@ export class ItemMoreOptionsComponent { } async archive() { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(this.cipher); + if (!repromptPassed) { + return; + } + const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "archiveItem" }, content: { key: "archiveItemConfirmDesc" }, diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html index 3c1278b4d44..225640137e8 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html @@ -34,13 +34,27 @@ <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> </a> </bit-item> - @if (userCanArchive() || showArchiveFilter()) { - <bit-item> - <a bit-item-content routerLink="/archive"> - {{ "archiveNoun" | i18n }} - <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> - </a> - </bit-item> + @if (showArchiveItem()) { + @if (userCanArchive()) { + <bit-item> + <a bit-item-content routerLink="/archive"> + {{ "archiveNoun" | i18n }} + <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> + </a> + </bit-item> + } @else { + <bit-item> + <a bit-item-content [routerLink]="userHasArchivedItems() ? '/archive' : '/premium'"> + <span class="tw-flex tw-items-center tw-gap-2"> + {{ "archiveNoun" | i18n }} + @if (!userHasArchivedItems()) { + <app-premium-badge></app-premium-badge> + } + </span> + <i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i> + </a> + </bit-item> + } } <bit-item> <a bit-item-content routerLink="/trash"> diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts index ff6e9b4065c..c6db820c232 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.ts @@ -2,14 +2,16 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { toSignal } from "@angular/core/rxjs-interop"; import { Router, RouterModule } from "@angular/router"; -import { firstValueFrom, switchMap } from "rxjs"; +import { firstValueFrom, map, switchMap } from "rxjs"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { NudgesService, NudgeType } from "@bitwarden/angular/vault"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { BadgeComponent, ItemModule, ToastOptions, ToastService } from "@bitwarden/components"; @@ -18,6 +20,7 @@ import BrowserPopupUtils from "../../../platform/browser/browser-popup-utils"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { BrowserPremiumUpgradePromptService } from "../services/browser-premium-upgrade-prompt.service"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -32,20 +35,28 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co PopOutComponent, ItemModule, BadgeComponent, + PremiumBadgeComponent, + ], + providers: [ + { provide: PremiumUpgradePromptService, useClass: BrowserPremiumUpgradePromptService }, ], }) export class VaultSettingsV2Component implements OnInit, OnDestroy { lastSync = "--"; private userId$ = this.accountService.activeAccount$.pipe(getUserId); - // Check if user is premium user, they will be able to archive items protected readonly userCanArchive = toSignal( this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId))), ); - // Check if user has archived items (does not check if user is premium) - protected readonly showArchiveFilter = toSignal( - this.userId$.pipe(switchMap((userId) => this.cipherArchiveService.showArchiveVault$(userId))), + protected readonly showArchiveItem = toSignal(this.cipherArchiveService.hasArchiveFlagEnabled$()); + + protected readonly userHasArchivedItems = toSignal( + this.userId$.pipe( + switchMap((userId) => + this.cipherArchiveService.archivedCiphers$(userId).pipe(map((c) => c.length > 0)), + ), + ), ); protected emptyVaultImportBadge$ = this.accountService.activeAccount$.pipe( From 898d514d5b5a0c93910ee28df71f6335f6d6363b Mon Sep 17 00:00:00 2001 From: Will Martin <contact@willmartian.com> Date: Tue, 25 Nov 2025 13:13:07 -0500 Subject: [PATCH 238/249] [CL-854] feat: add bit-header component to component library (#17662) Add new bit-header component to libs/components with: - Header component with left, center, and right content projection - Storybook stories for documentation - Export from component library index --- .../src/header/header.component.html | 35 ++++ .../components/src/header/header.component.ts | 19 ++ libs/components/src/header/header.stories.ts | 189 ++++++++++++++++++ libs/components/src/header/index.ts | 1 + libs/components/src/index.ts | 1 + 5 files changed, 245 insertions(+) create mode 100644 libs/components/src/header/header.component.html create mode 100644 libs/components/src/header/header.component.ts create mode 100644 libs/components/src/header/header.stories.ts create mode 100644 libs/components/src/header/index.ts diff --git a/libs/components/src/header/header.component.html b/libs/components/src/header/header.component.html new file mode 100644 index 00000000000..992dcbea4a7 --- /dev/null +++ b/libs/components/src/header/header.component.html @@ -0,0 +1,35 @@ +<header + class="-tw-mt-6 -tw-mx-8 tw-mb-3 tw-flex tw-flex-col tw-py-6 tw-px-8 has-[[data-tabs]:not(:empty)]:tw-border-0 has-[[data-tabs]:not(:empty)]:tw-border-b has-[[data-tabs]:not(:empty)]:tw-border-solid has-[[data-tabs]:not(:empty)]:tw-border-secondary-100 has-[[data-tabs]:not(:empty)]:tw-bg-background-alt has-[[data-tabs]:not(:empty)]:tw-pb-0" +> + <div class="tw-flex"> + <div class="tw-flex tw-min-w-0 tw-flex-1 tw-flex-col tw-gap-2"> + <ng-content select="[slot=breadcrumbs]"></ng-content> + <h1 + bitTypography="h1" + noMargin + class="tw-m-0 tw-mr-2 tw-leading-10 tw-flex tw-gap-1" + [title]="title()" + > + <div class="tw-truncate"> + @if (icon()) { + <i class="bwi {{ icon() }}" aria-hidden="true"></i> + } + + {{ title() }} + </div> + <div><ng-content select="[slot=title-suffix]"></ng-content></div> + </h1> + </div> + <div class="tw-ml-auto tw-flex tw-flex-col tw-gap-4"> + <div class="tw-flex tw-min-w-max tw-items-center tw-justify-end tw-gap-2"> + <ng-content></ng-content> + </div> + <div class="tw-ml-auto empty:tw-hidden"> + <ng-content select="[slot=secondary]"></ng-content> + </div> + </div> + </div> + <div data-tabs class="-tw-mx-4 -tw-mb-px empty:tw-hidden"> + <ng-content select="[slot=tabs]"></ng-content> + </div> +</header> diff --git a/libs/components/src/header/header.component.ts b/libs/components/src/header/header.component.ts new file mode 100644 index 00000000000..08cd91ea206 --- /dev/null +++ b/libs/components/src/header/header.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, input } from "@angular/core"; + +@Component({ + selector: "bit-header", + templateUrl: "./header.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, +}) +export class HeaderComponent { + /** + * The title of the page + */ + readonly title = input.required<string>(); + + /** + * Icon to show before the title + */ + readonly icon = input<string>(); +} diff --git a/libs/components/src/header/header.stories.ts b/libs/components/src/header/header.stories.ts new file mode 100644 index 00000000000..620f39a5dc3 --- /dev/null +++ b/libs/components/src/header/header.stories.ts @@ -0,0 +1,189 @@ +import { importProvidersFrom } from "@angular/core"; +import { RouterModule } from "@angular/router"; +import { + applicationConfig, + componentWrapperDecorator, + Meta, + moduleMetadata, + StoryObj, +} from "@storybook/angular"; + +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + NavigationModule, + TabsModule, + TypographyModule, +} from "@bitwarden/components"; + +import { I18nMockService } from "../utils"; + +import { HeaderComponent } from "./header.component"; + +export default { + title: "Component Library/Header", + component: HeaderComponent, + decorators: [ + componentWrapperDecorator( + (story) => `<div class="tw-min-h-screen tw-flex-1 tw-p-6 tw-text-main">${story}</div>`, + ), + moduleMetadata({ + imports: [ + HeaderComponent, + AvatarModule, + BreadcrumbsModule, + ButtonModule, + IconButtonModule, + IconModule, + InputModule, + MenuModule, + NavigationModule, + TabsModule, + TypographyModule, + ], + }), + applicationConfig({ + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService({ + moreBreadcrumbs: "More breadcrumbs", + loading: "Loading", + }); + }, + }, + importProvidersFrom( + RouterModule.forRoot( + [ + { path: "", redirectTo: "foo", pathMatch: "full" }, + { path: "foo", component: HeaderComponent }, + { path: "bar", component: HeaderComponent }, + ], + { useHash: true }, + ), + ), + ], + }), + ], +} as Meta; + +type Story = StoryObj<HeaderComponent>; + +export const KitchenSink: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <bit-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug"> + <bit-breadcrumbs slot="breadcrumbs"> + <bit-breadcrumb>Foo</bit-breadcrumb> + <bit-breadcrumb>Bar</bit-breadcrumb> + </bit-breadcrumbs> + <input + bitInput + placeholder="Ask Jeeves" + type="text" + /> + <button type="button" bitIconButton="bwi-filter" label="Switch products"></button> + <bit-avatar text="Will"></bit-avatar> + <button bitButton buttonType="primary">New</button> + <button bitButton slot="secondary">Click Me 🎉</button> + <bit-tab-nav-bar slot="tabs"> + <bit-tab-link [route]="['foo']">Foo</bit-tab-link> + <bit-tab-link [route]="['bar']">Bar</bit-tab-link> + </bit-tab-nav-bar> + </bit-header> + `, + }), +}; + +export const Basic: Story = { + render: (args: any) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" /> + `, + }), +}; + +export const WithLongTitle: Story = { + render: (arg: any) => ({ + props: arg, + template: /*html*/ ` + <bit-header title="LongTitleeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" icon="bwi-bug"> + <ng-container slot="title-suffix"><i class="bwi bwi-key"></i></ng-container> + </bit-header> + `, + }), +}; + +export const WithBreadcrumbs: Story = { + render: (args: any) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" class="tw-text-main"> + <bit-breadcrumbs slot="breadcrumbs"> + <bit-breadcrumb>Foo</bit-breadcrumb> + <bit-breadcrumb>Bar</bit-breadcrumb> + </bit-breadcrumbs> + </bit-header> + `, + }), +}; + +export const WithSearch: Story = { + render: (args: any) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" class="tw-text-main"> + <input + bitInput + placeholder="Ask Jeeves" + type="text" + /> + </bit-header> + `, + }), +}; + +export const WithSecondaryContent: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" class="tw-text-main"> + <button bitButton slot="secondary">Click Me 🎉</button> + </bit-header> + `, + }), +}; + +export const WithTabs: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" class="tw-text-main"> + <bit-tab-nav-bar slot="tabs"> + <bit-tab-link [route]="['foo']">Foo</bit-tab-link> + <bit-tab-link [route]="['bar']">Bar</bit-tab-link> + </bit-tab-nav-bar> + </bit-header> + `, + }), +}; + +export const WithTitleSuffixComponent: Story = { + render: (args) => ({ + props: args, + template: /*html*/ ` + <bit-header title="Foobar" icon="bwi-bug" class="tw-text-main"> + <ng-container slot="title-suffix"><i class="bwi bwi-spinner bwi-spin"></i></ng-container> + </bit-header> + `, + }), +}; diff --git a/libs/components/src/header/index.ts b/libs/components/src/header/index.ts new file mode 100644 index 00000000000..c2d38d375ed --- /dev/null +++ b/libs/components/src/header/index.ts @@ -0,0 +1 @@ +export * from "./header.component"; diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 5346747d3b2..410a96f1cb3 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -19,6 +19,7 @@ export * from "./dialog"; export * from "./disclosure"; export * from "./drawer"; export * from "./form-field"; +export * from "./header"; export * from "./icon-button"; export * from "./icon"; export * from "./icon-tile"; From eae894123dfe275371b565591a9c6f1e53715b5d Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 10:33:21 -0800 Subject: [PATCH 239/249] [PM-28376] - update copy for autofill confirmation dialog url list expand button (#17594) * update copy for autofill confirmation dialog url list expand button * fix tests --- apps/browser/src/_locales/en/messages.json | 3 ++ ...utofill-confirmation-dialog.component.html | 2 +- ...fill-confirmation-dialog.component.spec.ts | 53 +++++++++++++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 6009bcba1bd..fe979e129f4 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -597,6 +597,9 @@ "viewAll": { "message": "View all" }, + "showAll": { + "message": "Show all" + }, "viewLess": { "message": "View less" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html index 6f61c5fa446..88bff47191a 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.html @@ -24,7 +24,7 @@ class="tw-text-sm tw-font-medium tw-cursor-pointer" (click)="toggleSavedUrlExpandedState()" > - {{ (savedUrlsExpanded() ? "viewLess" : "viewAll") | i18n }} + {{ (savedUrlsExpanded() ? "showLess" : "showAll") | i18n }} </button> </div> <div class="tw-pt-2" [ngClass]="savedUrlsListClass()"> diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts index e40019d99b6..a28b8730109 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-confirmation-dialog/autofill-confirmation-dialog.component.spec.ts @@ -91,6 +91,11 @@ describe("AutofillConfirmationDialogComponent", () => { jest.resetAllMocks(); }); + const findShowAll = (inFx?: ComponentFixture<AutofillConfirmationDialogComponent>) => + (inFx || fixture).nativeElement.querySelector( + "button.tw-text-sm.tw-font-medium.tw-cursor-pointer", + ) as HTMLButtonElement | null; + it("normalizes currentUrl and savedUrls via Utils.getHostname", () => { expect(Utils.getHostname).toHaveBeenCalledTimes(1 + (params.savedUrls?.length ?? 0)); expect(component.currentUrl()).toBe("example.com"); @@ -191,21 +196,47 @@ describe("AutofillConfirmationDialogComponent", () => { expect(text).toContain("two.example.com"); }); - it("shows the 'view all' button when savedUrls > 1 and toggles the button text when clicked", () => { - const findViewAll = () => - fixture.nativeElement.querySelector( - "button.tw-text-sm.tw-font-medium.tw-cursor-pointer", - ) as HTMLButtonElement | null; - - let btn = findViewAll(); + it("shows the 'show all' button when savedUrls > 1", () => { + const btn = findShowAll(); expect(btn).toBeTruthy(); + expect(btn!.textContent).toContain("showAll"); + }); + it('hides the "show all" button when savedUrls is empty', async () => { + const newParams: AutofillConfirmationDialogParams = { + currentUrl: "https://bitwarden.com/help", + savedUrls: [], + }; + + const { fixture: vf } = await createFreshFixture({ params: newParams }); + vf.detectChanges(); + const btn = findShowAll(vf); + expect(btn).toBeNull(); + }); + + it("handles toggling of the 'show all' button correctly", async () => { + const { fixture: vf, component: vc } = await createFreshFixture(); + + let btn = findShowAll(vf); + expect(btn).toBeTruthy(); + expect(vc.savedUrlsExpanded()).toBe(false); + expect(btn!.textContent).toContain("showAll"); + + // click to expand btn!.click(); - fixture.detectChanges(); + vf.detectChanges(); - btn = findViewAll(); - expect(btn!.textContent).toContain("viewLess"); - expect(component.savedUrlsExpanded()).toBe(true); + btn = findShowAll(vf); + expect(btn!.textContent).toContain("showLess"); + expect(vc.savedUrlsExpanded()).toBe(true); + + // click to collapse + btn!.click(); + vf.detectChanges(); + + btn = findShowAll(vf); + expect(btn!.textContent).toContain("showAll"); + expect(vc.savedUrlsExpanded()).toBe(false); }); it("shows autofillWithoutAdding text on autofill button when viewOnly is false", () => { From 3de3bee08ff8d77b0333997b7c60202accd824f4 Mon Sep 17 00:00:00 2001 From: Daniel Riera <driera@livefront.com> Date: Tue, 25 Nov 2025 13:42:46 -0500 Subject: [PATCH 240/249] [PM-27821]Add validation of extension origin for uses of window.postMessage (#17476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PM-27821 - Replace chrome.runtime.getURL() with BrowserApi.getRuntimeURL() for consistency - Add extension origin validation for all window.postMessage calls - Implement token-based authentication for inline menu communications - Add message source validation (event.source === globalThis.parent) - Add command presence validation (- Update notification bar to validate message origins and commands - Add extensionOrigin property to services using postMessage - Generate session tokens for inline menu containers (32-char random) - Validate tokens in message handlers to prevent unauthorized commands * Add explicit token validation * only set when receiving the trusted initNotificationBar message * await windowmessageorigin before posting to parent * fix tests * the parent must include its origin in the message for notification bar race condition * reduce if statements to one block and comment * extract parentOrigin from the URL and set windoMessageOrigin accordingly * consolidate if statements * add bar.spec file * fix merge conflict --- .../background/notification.background.ts | 2 +- .../autofill/background/overlay.background.ts | 6 +- .../content/content-message-handler.spec.ts | 14 +- .../abstractions/notification-bar.ts | 1 + .../src/autofill/notification/bar.spec.ts | 121 ++++++++++++++++ apps/browser/src/autofill/notification/bar.ts | 34 ++++- .../autofill-inline-menu-button.ts | 1 + .../autofill-inline-menu-container.ts | 2 +- .../abstractions/autofill-inline-menu-list.ts | 1 + ...utofill-inline-menu-iframe.service.spec.ts | 12 +- .../autofill-inline-menu-iframe.service.ts | 10 +- .../autofill-inline-menu-button.spec.ts | 21 +-- .../list/autofill-inline-menu-list.spec.ts | 129 ++++++++++++------ .../autofill-inline-menu-container.ts | 11 +- .../autofill-inline-menu-page-element.ts | 42 ++++-- ...notifications-content.service.spec.ts.snap | 2 +- ...rlay-notifications-content.service.spec.ts | 5 +- .../overlay-notifications-content.service.ts | 16 ++- .../src/autofill/spec/autofill-mocks.ts | 2 + .../src/autofill/spec/testing-utils.ts | 8 +- 20 files changed, 344 insertions(+), 96 deletions(-) create mode 100644 apps/browser/src/autofill/notification/bar.spec.ts diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index de1514f0342..547c5ba1575 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1344,7 +1344,7 @@ export default class NotificationBackground { return; } - const extensionUrl = chrome.runtime.getURL("popup/index.html"); + const extensionUrl = BrowserApi.getRuntimeURL("popup/index.html"); const unlockPopoutTabs = (await BrowserApi.tabsQuery({ url: `${extensionUrl}*` })).filter( (tab) => tab.url?.includes(`singleActionPopout=${AuthPopoutType.unlockExtension}`), ); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 0eb7d070de3..af8141f1ab8 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -2949,13 +2949,13 @@ export class OverlayBackground implements OverlayBackgroundInterface { (await this.checkFocusedFieldHasValue(port.sender.tab)) && (await this.shouldShowSaveLoginInlineMenuList(port.sender.tab)); - const iframeUrl = chrome.runtime.getURL( + const iframeUrl = BrowserApi.getRuntimeURL( `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.html`, ); - const styleSheetUrl = chrome.runtime.getURL( + const styleSheetUrl = BrowserApi.getRuntimeURL( `overlay/menu-${isInlineMenuListPort ? "list" : "button"}.css`, ); - const extensionOrigin = new URL(iframeUrl).origin; + const extensionOrigin = iframeUrl ? new URL(iframeUrl).origin : null; this.postMessageToPort(port, { command: `initAutofillInlineMenu${isInlineMenuListPort ? "List" : "Button"}`, diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index fe023f344d6..874e1cc76ff 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -56,7 +56,11 @@ describe("ContentMessageHandler", () => { }); it("sends an authResult message", () => { - postWindowMessage({ command: "authResult", lastpass: true, code: "code", state: "state" }); + postWindowMessage( + { command: "authResult", lastpass: true, code: "code", state: "state" }, + "https://localhost/", + window, + ); expect(sendMessageSpy).toHaveBeenCalledWith({ command: "authResult", @@ -68,7 +72,11 @@ describe("ContentMessageHandler", () => { }); it("sends a webAuthnResult message", () => { - postWindowMessage({ command: "webAuthnResult", data: "data", remember: true }); + postWindowMessage( + { command: "webAuthnResult", data: "data", remember: true }, + "https://localhost/", + window, + ); expect(sendMessageSpy).toHaveBeenCalledWith({ command: "webAuthnResult", @@ -82,7 +90,7 @@ describe("ContentMessageHandler", () => { const mockCode = "mockCode"; const command = "duoResult"; - postWindowMessage({ command: command, code: mockCode }); + postWindowMessage({ command: command, code: mockCode }, "https://localhost/", window); expect(sendMessageSpy).toHaveBeenCalledWith({ command: command, diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 7881d2f1cac..b23c3c17abb 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -51,6 +51,7 @@ type NotificationBarWindowMessage = { }; error?: string; initData?: NotificationBarIframeInitData; + parentOrigin?: string; }; type NotificationBarWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/notification/bar.spec.ts b/apps/browser/src/autofill/notification/bar.spec.ts new file mode 100644 index 00000000000..ae60e2efc91 --- /dev/null +++ b/apps/browser/src/autofill/notification/bar.spec.ts @@ -0,0 +1,121 @@ +import { mock } from "jest-mock-extended"; + +import { postWindowMessage } from "../spec/testing-utils"; + +import { NotificationBarWindowMessage } from "./abstractions/notification-bar"; +import "./bar"; + +jest.mock("lit", () => ({ render: jest.fn() })); +jest.mock("@lit-labs/signals", () => ({ + signal: jest.fn((testValue) => ({ get: (): typeof testValue => testValue })), +})); +jest.mock("../content/components/notification/container", () => ({ + NotificationContainer: jest.fn(), +})); + +describe("NotificationBar iframe handleWindowMessage security", () => { + const trustedOrigin = "http://localhost"; + const maliciousOrigin = "https://malicious.com"; + + const createMessage = ( + overrides: Partial<NotificationBarWindowMessage> = {}, + ): NotificationBarWindowMessage => ({ + command: "initNotificationBar", + ...overrides, + }); + + beforeEach(() => { + Object.defineProperty(globalThis, "location", { + value: { search: `?parentOrigin=${encodeURIComponent(trustedOrigin)}` }, + writable: true, + configurable: true, + }); + Object.defineProperty(globalThis, "parent", { + value: mock<Window>(), + writable: true, + configurable: true, + }); + globalThis.dispatchEvent(new Event("load")); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it.each([ + { + description: "not from parent window", + message: () => createMessage(), + origin: trustedOrigin, + source: () => mock<Window>(), + }, + { + description: "with mismatched origin", + message: () => createMessage(), + origin: maliciousOrigin, + source: () => globalThis.parent, + }, + { + description: "without command field", + message: () => ({}), + origin: trustedOrigin, + source: () => globalThis.parent, + }, + { + description: "initNotificationBar with mismatched parentOrigin", + message: () => createMessage({ parentOrigin: maliciousOrigin }), + origin: trustedOrigin, + source: () => globalThis.parent, + }, + { + description: "when windowMessageOrigin is not set", + message: () => createMessage(), + origin: "different-origin", + source: () => globalThis.parent, + resetOrigin: true, + }, + { + description: "with null source", + message: () => createMessage(), + origin: trustedOrigin, + source: (): null => null, + }, + { + description: "with unknown command", + message: () => createMessage({ command: "unknownCommand" }), + origin: trustedOrigin, + source: () => globalThis.parent, + }, + ])("should reject messages $description", ({ message, origin, source, resetOrigin }) => { + if (resetOrigin) { + Object.defineProperty(globalThis, "location", { + value: { search: "" }, + writable: true, + configurable: true, + }); + } + const spy = jest.spyOn(globalThis.parent, "postMessage").mockImplementation(); + postWindowMessage(message(), origin, source()); + expect(spy).not.toHaveBeenCalled(); + }); + + it("should accept and handle valid trusted messages", () => { + const spy = jest.spyOn(globalThis.parent, "postMessage").mockImplementation(); + spy.mockClear(); + + const validMessage = createMessage({ + parentOrigin: trustedOrigin, + initData: { + type: "change", + isVaultLocked: false, + removeIndividualVault: false, + importType: null, + launchTimestamp: Date.now(), + }, + }); + postWindowMessage(validMessage, trustedOrigin, globalThis.parent); + expect(validMessage.command).toBe("initNotificationBar"); + expect(validMessage.parentOrigin).toBe(trustedOrigin); + expect(validMessage.initData).toBeDefined(); + }); +}); diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 3673a9f7321..333f8d5e534 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -24,6 +24,13 @@ import { let notificationBarIframeInitData: NotificationBarIframeInitData = {}; let windowMessageOrigin: string; +const urlParams = new URLSearchParams(globalThis.location.search); +const trustedParentOrigin = urlParams.get("parentOrigin"); + +if (trustedParentOrigin) { + windowMessageOrigin = trustedParentOrigin; +} + const notificationBarWindowMessageHandlers: NotificationBarWindowMessageHandlers = { initNotificationBar: ({ message }) => initNotificationBar(message), saveCipherAttemptCompleted: ({ message }) => handleSaveCipherConfirmation(message), @@ -395,15 +402,27 @@ function setupWindowMessageListener() { } function handleWindowMessage(event: MessageEvent) { - if (!windowMessageOrigin) { - windowMessageOrigin = event.origin; - } - - if (event.origin !== windowMessageOrigin) { + if (event?.source !== globalThis.parent) { return; } const message = event.data as NotificationBarWindowMessage; + if (!message?.command) { + return; + } + + if (!windowMessageOrigin || event.origin !== windowMessageOrigin) { + return; + } + + if ( + message.command === "initNotificationBar" && + message.parentOrigin && + message.parentOrigin !== event.origin + ) { + return; + } + const handler = notificationBarWindowMessageHandlers[message.command]; if (!handler) { return; @@ -431,5 +450,8 @@ function getResolvedTheme(theme: Theme) { } function postMessageToParent(message: NotificationBarWindowMessage) { - globalThis.parent.postMessage(message, windowMessageOrigin || "*"); + if (!windowMessageOrigin) { + return; + } + globalThis.parent.postMessage(message, windowMessageOrigin); } diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-button.ts index 642e7dd24e9..0836ecf5ff1 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-button.ts @@ -10,6 +10,7 @@ export type InitAutofillInlineMenuButtonMessage = UpdateAuthStatusMessage & { styleSheetUrl: string; translations: Record<string, string>; portKey: string; + token: string; }; export type AutofillInlineMenuButtonWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts index 64fa8dde124..98fd84373a8 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-container.ts @@ -5,7 +5,7 @@ import { InlineMenuCipherData } from "../../../background/abstractions/overlay.b export type AutofillInlineMenuContainerMessage = { command: string; portKey: string; - token?: string; + token: string; }; export type InitAutofillInlineMenuElementMessage = AutofillInlineMenuContainerMessage & { diff --git a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts index f5e1fe08850..cf778ef7892 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/abstractions/autofill-inline-menu-list.ts @@ -27,6 +27,7 @@ export type InitAutofillInlineMenuListMessage = AutofillInlineMenuListMessage & showInlineMenuAccountCreation?: boolean; showPasskeysLabels?: boolean; portKey: string; + token: string; generatedPassword?: string; showSaveLoginMenu?: boolean; }; diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts index 9f2947c2e99..3bb86ee7876 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.spec.ts @@ -191,7 +191,7 @@ describe("AutofillInlineMenuIframeService", () => { expect( autofillInlineMenuIframeService["iframe"].contentWindow.postMessage, - ).toHaveBeenCalledWith(message, "*"); + ).toHaveBeenCalledWith(message, autofillInlineMenuIframeService["extensionOrigin"]); }); it("handles port messages that are registered with the message handlers and does not pass the message on to the iframe", () => { @@ -217,7 +217,7 @@ describe("AutofillInlineMenuIframeService", () => { expect(autofillInlineMenuIframeService["portKey"]).toBe(portKey); expect( autofillInlineMenuIframeService["iframe"].contentWindow.postMessage, - ).toHaveBeenCalledWith(message, "*"); + ).toHaveBeenCalledWith(message, autofillInlineMenuIframeService["extensionOrigin"]); }); }); @@ -242,7 +242,7 @@ describe("AutofillInlineMenuIframeService", () => { expect(updateElementStylesSpy).not.toHaveBeenCalled(); expect( autofillInlineMenuIframeService["iframe"].contentWindow.postMessage, - ).toHaveBeenCalledWith(message, "*"); + ).toHaveBeenCalledWith(message, autofillInlineMenuIframeService["extensionOrigin"]); }); it("sets a light theme based on the user's system preferences", () => { @@ -262,7 +262,7 @@ describe("AutofillInlineMenuIframeService", () => { command: "initAutofillInlineMenuList", theme: ThemeType.Light, }, - "*", + autofillInlineMenuIframeService["extensionOrigin"], ); }); @@ -283,7 +283,7 @@ describe("AutofillInlineMenuIframeService", () => { command: "initAutofillInlineMenuList", theme: ThemeType.Dark, }, - "*", + autofillInlineMenuIframeService["extensionOrigin"], ); }); @@ -387,7 +387,7 @@ describe("AutofillInlineMenuIframeService", () => { command: "updateAutofillInlineMenuColorScheme", colorScheme: "normal", }, - "*", + autofillInlineMenuIframeService["extensionOrigin"], ); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts index 9a9821f643c..8b1423b1290 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/iframe-content/autofill-inline-menu-iframe.service.ts @@ -3,6 +3,7 @@ import { EVENTS } from "@bitwarden/common/autofill/constants"; import { ThemeTypes } from "@bitwarden/common/platform/enums"; +import { BrowserApi } from "../../../../platform/browser/browser-api"; import { sendExtensionMessage, setElementStyles } from "../../../utils"; import { BackgroundPortMessageHandlers, @@ -15,6 +16,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe private readonly sendExtensionMessage = sendExtensionMessage; private port: chrome.runtime.Port | null = null; private portKey: string; + private readonly extensionOrigin: string; private iframeMutationObserver: MutationObserver; private iframe: HTMLIFrameElement; private ariaAlertElement: HTMLDivElement; @@ -69,6 +71,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe private iframeTitle: string, private ariaAlert?: string, ) { + this.extensionOrigin = BrowserApi.getRuntimeURL("")?.slice(0, -1); this.iframeMutationObserver = new MutationObserver(this.handleMutations); } @@ -81,7 +84,7 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe * that is declared. */ initMenuIframe() { - this.defaultIframeAttributes.src = chrome.runtime.getURL("overlay/menu.html"); + this.defaultIframeAttributes.src = BrowserApi.getRuntimeURL("overlay/menu.html"); this.defaultIframeAttributes.title = this.iframeTitle; this.iframe = globalThis.document.createElement("iframe"); @@ -259,7 +262,10 @@ export class AutofillInlineMenuIframeService implements AutofillInlineMenuIframe } private postMessageToIFrame(message: any) { - this.iframe.contentWindow?.postMessage({ portKey: this.portKey, ...message }, "*"); + this.iframe.contentWindow?.postMessage( + { portKey: this.portKey, ...message }, + this.extensionOrigin, + ); } /** diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.spec.ts index 7fa07850f00..10f6c905342 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/autofill-inline-menu-button.spec.ts @@ -1,5 +1,6 @@ import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { createInitAutofillInlineMenuButtonMessageMock } from "../../../../spec/autofill-mocks"; import { flushPromises, postWindowMessage } from "../../../../spec/testing-utils"; @@ -10,11 +11,11 @@ describe("AutofillInlineMenuButton", () => { let autofillInlineMenuButton: AutofillInlineMenuButton; const portKey: string = "inlineMenuButtonPortKey"; + const expectedOrigin = BrowserApi.getRuntimeURL("")?.slice(0, -1) || "chrome-extension://id"; beforeEach(() => { document.body.innerHTML = `<autofill-inline-menu-button></autofill-inline-menu-button>`; autofillInlineMenuButton = document.querySelector("autofill-inline-menu-button"); - autofillInlineMenuButton["messageOrigin"] = "https://localhost/"; jest.spyOn(globalThis.document, "createElement"); jest.spyOn(globalThis.parent, "postMessage"); }); @@ -56,8 +57,8 @@ describe("AutofillInlineMenuButton", () => { autofillInlineMenuButton["buttonElement"].click(); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "autofillInlineMenuButtonClicked", portKey }, - "*", + { command: "autofillInlineMenuButtonClicked", portKey, token: "test-token" }, + expectedOrigin, ); }); }); @@ -70,7 +71,7 @@ describe("AutofillInlineMenuButton", () => { it("does not post a message to close the autofill inline menu if the element is focused during the focus check", async () => { jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true); - postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused", token: "test-token" }); await flushPromises(); expect(globalThis.parent.postMessage).not.toHaveBeenCalledWith({ @@ -84,7 +85,7 @@ describe("AutofillInlineMenuButton", () => { .spyOn(autofillInlineMenuButton["buttonElement"], "querySelector") .mockReturnValue(autofillInlineMenuButton["buttonElement"]); - postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused", token: "test-token" }); await flushPromises(); expect(globalThis.parent.postMessage).not.toHaveBeenCalledWith({ @@ -98,7 +99,7 @@ describe("AutofillInlineMenuButton", () => { jest .spyOn(autofillInlineMenuButton["buttonElement"], "querySelector") .mockReturnValue(autofillInlineMenuButton["buttonElement"]); - postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused", token: "test-token" }); await flushPromises(); globalThis.document.dispatchEvent(new MouseEvent("mouseout")); @@ -113,12 +114,12 @@ describe("AutofillInlineMenuButton", () => { jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(false); jest.spyOn(autofillInlineMenuButton["buttonElement"], "querySelector").mockReturnValue(null); - postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuButtonFocused", token: "test-token" }); await flushPromises(); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "triggerDelayedAutofillInlineMenuClosure", portKey }, - "*", + { command: "triggerDelayedAutofillInlineMenuClosure", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -128,6 +129,7 @@ describe("AutofillInlineMenuButton", () => { postWindowMessage({ command: "updateAutofillInlineMenuButtonAuthStatus", authStatus: AuthenticationStatus.Unlocked, + token: "test-token", }); await flushPromises(); @@ -143,6 +145,7 @@ describe("AutofillInlineMenuButton", () => { postWindowMessage({ command: "updateAutofillInlineMenuColorScheme", colorScheme: "dark", + token: "test-token", }); await flushPromises(); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts index b4e480797da..81bf7240230 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.spec.ts @@ -3,6 +3,7 @@ import { mock } from "jest-mock-extended"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { InlineMenuCipherData } from "../../../../background/abstractions/overlay.background"; import { createAutofillOverlayCipherDataMock, @@ -23,6 +24,7 @@ describe("AutofillInlineMenuList", () => { let autofillInlineMenuList: AutofillInlineMenuList | null; const portKey: string = "inlineMenuListPortKey"; + const expectedOrigin = BrowserApi.getRuntimeURL("")?.slice(0, -1) || "chrome-extension://id"; const events: { eventName: any; callback: any }[] = []; beforeEach(() => { @@ -67,8 +69,8 @@ describe("AutofillInlineMenuList", () => { unlockButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "unlockVault", portKey }, - "*", + { command: "unlockVault", portKey, token: "test-token" }, + expectedOrigin, ); }); }); @@ -134,8 +136,13 @@ describe("AutofillInlineMenuList", () => { addVaultItemButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "addNewVaultItem", portKey, addNewCipherType: CipherType.Login }, - "*", + { + command: "addNewVaultItem", + portKey, + addNewCipherType: CipherType.Login, + token: "test-token", + }, + expectedOrigin, ); }); }); @@ -324,8 +331,9 @@ describe("AutofillInlineMenuList", () => { inlineMenuCipherId: "1", usePasskey: false, portKey, + token: "test-token", }, - "*", + expectedOrigin, ); }); @@ -492,8 +500,13 @@ describe("AutofillInlineMenuList", () => { viewCipherButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "viewSelectedCipher", inlineMenuCipherId: "1", portKey }, - "*", + { + command: "viewSelectedCipher", + inlineMenuCipherId: "1", + portKey, + token: "test-token", + }, + expectedOrigin, ); }); @@ -581,8 +594,13 @@ describe("AutofillInlineMenuList", () => { newVaultItemButtonSpy.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "addNewVaultItem", portKey, addNewCipherType: CipherType.Login }, - "*", + { + command: "addNewVaultItem", + portKey, + addNewCipherType: CipherType.Login, + token: "test-token", + }, + expectedOrigin, ); }); @@ -826,8 +844,8 @@ describe("AutofillInlineMenuList", () => { fillGeneratedPasswordButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "fillGeneratedPassword", portKey }, - "*", + { command: "fillGeneratedPassword", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -843,7 +861,7 @@ describe("AutofillInlineMenuList", () => { expect(globalThis.parent.postMessage).not.toHaveBeenCalledWith( { command: "fillGeneratedPassword", portKey }, - "*", + expectedOrigin, ); }); @@ -857,8 +875,8 @@ describe("AutofillInlineMenuList", () => { ); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "fillGeneratedPassword", portKey }, - "*", + { command: "fillGeneratedPassword", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -896,8 +914,8 @@ describe("AutofillInlineMenuList", () => { refreshGeneratedPasswordButton.dispatchEvent(new Event("click")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "refreshGeneratedPassword", portKey }, - "*", + { command: "refreshGeneratedPassword", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -913,7 +931,7 @@ describe("AutofillInlineMenuList", () => { expect(globalThis.parent.postMessage).not.toHaveBeenCalledWith( { command: "refreshGeneratedPassword", portKey }, - "*", + expectedOrigin, ); }); @@ -927,8 +945,8 @@ describe("AutofillInlineMenuList", () => { ); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "refreshGeneratedPassword", portKey }, - "*", + { command: "refreshGeneratedPassword", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -972,7 +990,7 @@ describe("AutofillInlineMenuList", () => { it("does not post a `checkAutofillInlineMenuButtonFocused` message to the parent if the inline menu is currently focused", () => { jest.spyOn(globalThis.document, "hasFocus").mockReturnValue(true); - postWindowMessage({ command: "checkAutofillInlineMenuListFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuListFocused", token: "test-token" }); expect(globalThis.parent.postMessage).not.toHaveBeenCalled(); }); @@ -983,7 +1001,7 @@ describe("AutofillInlineMenuList", () => { .spyOn(autofillInlineMenuList["inlineMenuListContainer"], "querySelector") .mockReturnValue(autofillInlineMenuList["inlineMenuListContainer"]); - postWindowMessage({ command: "checkAutofillInlineMenuListFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuListFocused", token: "test-token" }); expect(globalThis.parent.postMessage).not.toHaveBeenCalled(); }); @@ -994,7 +1012,7 @@ describe("AutofillInlineMenuList", () => { jest .spyOn(autofillInlineMenuList["inlineMenuListContainer"], "querySelector") .mockReturnValue(autofillInlineMenuList["inlineMenuListContainer"]); - postWindowMessage({ command: "checkAutofillInlineMenuListFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuListFocused", token: "test-token" }); await flushPromises(); globalThis.document.dispatchEvent(new MouseEvent("mouseout")); @@ -1010,11 +1028,11 @@ describe("AutofillInlineMenuList", () => { .spyOn(autofillInlineMenuList["inlineMenuListContainer"], "querySelector") .mockReturnValue(null); - postWindowMessage({ command: "checkAutofillInlineMenuListFocused" }); + postWindowMessage({ command: "checkAutofillInlineMenuListFocused", token: "test-token" }); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "checkAutofillInlineMenuButtonFocused", portKey }, - "*", + { command: "checkAutofillInlineMenuButtonFocused", portKey, token: "test-token" }, + expectedOrigin, ); }); @@ -1022,7 +1040,7 @@ describe("AutofillInlineMenuList", () => { postWindowMessage(createInitAutofillInlineMenuListMessageMock()); const updateCiphersSpy = jest.spyOn(autofillInlineMenuList as any, "updateListItems"); - postWindowMessage({ command: "updateAutofillInlineMenuListCiphers" }); + postWindowMessage({ command: "updateAutofillInlineMenuListCiphers", token: "test-token" }); expect(updateCiphersSpy).toHaveBeenCalled(); }); @@ -1062,7 +1080,10 @@ describe("AutofillInlineMenuList", () => { postWindowMessage(createInitAutofillInlineMenuListMessageMock()); await flushPromises(); - postWindowMessage({ command: "updateAutofillInlineMenuGeneratedPassword" }); + postWindowMessage({ + command: "updateAutofillInlineMenuGeneratedPassword", + token: "test-token", + }); expect(buildColorizedPasswordElementSpy).not.toHaveBeenCalled(); }); @@ -1074,6 +1095,7 @@ describe("AutofillInlineMenuList", () => { postWindowMessage({ command: "updateAutofillInlineMenuGeneratedPassword", generatedPassword, + token: "test-token", }); expect(buildPasswordGeneratorSpy).toHaveBeenCalled(); @@ -1090,6 +1112,7 @@ describe("AutofillInlineMenuList", () => { postWindowMessage({ command: "updateAutofillInlineMenuGeneratedPassword", generatedPassword, + token: "test-token", }); expect(buildPasswordGeneratorSpy).toHaveBeenCalledTimes(1); @@ -1115,7 +1138,7 @@ describe("AutofillInlineMenuList", () => { ); await flushPromises(); - postWindowMessage({ command: "showSaveLoginInlineMenuList" }); + postWindowMessage({ command: "showSaveLoginInlineMenuList", token: "test-token" }); expect(buildSaveLoginInlineMenuSpy).not.toHaveBeenCalled(); }); @@ -1124,7 +1147,7 @@ describe("AutofillInlineMenuList", () => { postWindowMessage(createInitAutofillInlineMenuListMessageMock()); await flushPromises(); - postWindowMessage({ command: "showSaveLoginInlineMenuList" }); + postWindowMessage({ command: "showSaveLoginInlineMenuList", token: "test-token" }); expect(buildSaveLoginInlineMenuSpy).toHaveBeenCalled(); }); @@ -1143,7 +1166,7 @@ describe("AutofillInlineMenuList", () => { "setAttribute", ); - postWindowMessage({ command: "focusAutofillInlineMenuList" }); + postWindowMessage({ command: "focusAutofillInlineMenuList", token: "test-token" }); expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("role", "dialog"); expect(inlineMenuContainerSetAttributeSpy).toHaveBeenCalledWith("aria-modal", "true"); @@ -1161,7 +1184,7 @@ describe("AutofillInlineMenuList", () => { autofillInlineMenuList["inlineMenuListContainer"].querySelector("#unlock-button"); jest.spyOn(unlockButton as HTMLElement, "focus"); - postWindowMessage({ command: "focusAutofillInlineMenuList" }); + postWindowMessage({ command: "focusAutofillInlineMenuList", token: "test-token" }); expect((unlockButton as HTMLElement).focus).toBeCalled(); }); @@ -1173,7 +1196,7 @@ describe("AutofillInlineMenuList", () => { autofillInlineMenuList["inlineMenuListContainer"].querySelector("#new-item-button"); jest.spyOn(newItemButton as HTMLElement, "focus"); - postWindowMessage({ command: "focusAutofillInlineMenuList" }); + postWindowMessage({ command: "focusAutofillInlineMenuList", token: "test-token" }); expect((newItemButton as HTMLElement).focus).toBeCalled(); }); @@ -1184,7 +1207,7 @@ describe("AutofillInlineMenuList", () => { autofillInlineMenuList["inlineMenuListContainer"].querySelector(".fill-cipher-button"); jest.spyOn(firstCipherItem as HTMLElement, "focus"); - postWindowMessage({ command: "focusAutofillInlineMenuList" }); + postWindowMessage({ command: "focusAutofillInlineMenuList", token: "test-token" }); expect((firstCipherItem as HTMLElement).focus).toBeCalled(); }); @@ -1197,8 +1220,8 @@ describe("AutofillInlineMenuList", () => { globalThis.dispatchEvent(new Event("blur")); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "autofillInlineMenuBlurred", portKey }, - "*", + { command: "autofillInlineMenuBlurred", portKey, token: "test-token" }, + expectedOrigin, ); }); }); @@ -1220,8 +1243,13 @@ describe("AutofillInlineMenuList", () => { ); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "redirectAutofillInlineMenuFocusOut", direction: "previous", portKey }, - "*", + { + command: "redirectAutofillInlineMenuFocusOut", + direction: "previous", + portKey, + token: "test-token", + }, + expectedOrigin, ); }); @@ -1229,8 +1257,13 @@ describe("AutofillInlineMenuList", () => { globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Tab" })); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "redirectAutofillInlineMenuFocusOut", direction: "next", portKey }, - "*", + { + command: "redirectAutofillInlineMenuFocusOut", + direction: "next", + portKey, + token: "test-token", + }, + expectedOrigin, ); }); @@ -1238,8 +1271,13 @@ describe("AutofillInlineMenuList", () => { globalThis.document.dispatchEvent(new KeyboardEvent("keydown", { code: "Escape" })); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "redirectAutofillInlineMenuFocusOut", direction: "current", portKey }, - "*", + { + command: "redirectAutofillInlineMenuFocusOut", + direction: "current", + portKey, + token: "test-token", + }, + expectedOrigin, ); }); }); @@ -1274,8 +1312,13 @@ describe("AutofillInlineMenuList", () => { autofillInlineMenuList["handleResizeObserver"](entries as unknown as ResizeObserverEntry[]); expect(globalThis.parent.postMessage).toHaveBeenCalledWith( - { command: "updateAutofillInlineMenuListHeight", styles: { height: "300px" }, portKey }, - "*", + { + command: "updateAutofillInlineMenuListHeight", + styles: { height: "300px" }, + portKey, + token: "test-token", + }, + expectedOrigin, ); }); }); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts index aea6ef30b99..6c61cfae6b4 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/autofill-inline-menu-container.ts @@ -1,5 +1,6 @@ import { EVENTS } from "@bitwarden/common/autofill/constants"; +import { BrowserApi } from "../../../../../platform/browser/browser-api"; import { generateRandomChars, setElementStyles } from "../../../../utils"; import { InitAutofillInlineMenuElementMessage, @@ -73,7 +74,7 @@ export class AutofillInlineMenuContainer { constructor() { this.token = generateRandomChars(32); - this.extensionOrigin = chrome.runtime.getURL("").slice(0, -1); + this.extensionOrigin = BrowserApi.getRuntimeURL("")?.slice(0, -1); globalThis.addEventListener("message", this.handleWindowMessage); } @@ -203,6 +204,9 @@ export class AutofillInlineMenuContainer { */ private handleWindowMessage = (event: MessageEvent<AutofillInlineMenuContainerWindowMessage>) => { const message = event.data; + if (!message?.command) { + return; + } if (this.isForeignWindowMessage(event)) { return; } @@ -287,7 +291,10 @@ export class AutofillInlineMenuContainer { * every time the inline menu container is recreated. * */ - private isValidSessionToken(message: { token?: string }): boolean { + private isValidSessionToken(message: { token: string }): boolean { + if (!this.token || !message?.token || !message?.token.length) { + return false; + } return message.token === this.token; } diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts index ea77e3e434d..5df6e7cd190 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/shared/autofill-inline-menu-page-element.ts @@ -38,12 +38,8 @@ export class AutofillInlineMenuPageElement extends HTMLElement { styleSheetUrl: string, translations: Record<string, string>, portKey: string, - token?: string, ): Promise<HTMLLinkElement> { this.portKey = portKey; - if (token) { - this.token = token; - } this.translations = translations; globalThis.document.documentElement.setAttribute("lang", this.getTranslation("locale")); @@ -63,11 +59,16 @@ export class AutofillInlineMenuPageElement extends HTMLElement { * @param message - The message to post */ protected postMessageToParent(message: AutofillInlineMenuPageElementWindowMessage) { - const messageWithAuth: Record<string, unknown> = { portKey: this.portKey, ...message }; - if (this.token) { - messageWithAuth.token = this.token; + // never send messages containing authentication tokens without a valid token and an established messageOrigin + if (!this.token || !this.messageOrigin) { + return; } - globalThis.parent.postMessage(messageWithAuth, "*"); + const messageWithAuth: Record<string, unknown> = { + portKey: this.portKey, + ...message, + token: this.token, + }; + globalThis.parent.postMessage(messageWithAuth, this.messageOrigin); } /** @@ -105,6 +106,10 @@ export class AutofillInlineMenuPageElement extends HTMLElement { return; } + if (event.source !== globalThis.parent) { + return; + } + if (!this.messageOrigin) { this.messageOrigin = event.origin; } @@ -115,12 +120,23 @@ export class AutofillInlineMenuPageElement extends HTMLElement { const message = event?.data; - if ( - message?.token && - (message?.command === "initAutofillInlineMenuButton" || - message?.command === "initAutofillInlineMenuList") - ) { + if (!message?.command) { + return; + } + + const isInitCommand = + message.command === "initAutofillInlineMenuButton" || + message.command === "initAutofillInlineMenuList"; + + if (isInitCommand) { + if (!message?.token) { + return; + } this.token = message.token; + } else { + if (!this.token || !message?.token || message.token !== this.token) { + return; + } } const handler = this.windowMessageHandlers[message?.command]; diff --git a/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap b/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap index 39ca68d912c..cfcedc9da7a 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/notifications/content/__snapshots__/overlay-notifications-content.service.spec.ts.snap @@ -7,7 +7,7 @@ exports[`OverlayNotificationsContentService opening the notification bar creates > <iframe id="bit-notification-bar-iframe" - src="chrome-extension://id/notification/bar.html" + src="chrome-extension://id/notification/bar.html?parentOrigin=http%3A%2F%2Flocalhost" style="width: 100% !important; height: 100% !important; border: 0px !important; display: block !important; position: relative !important; transition: transform 0.15s ease-out, opacity 0.15s ease !important; border-radius: 4px !important; color-scheme: auto !important; transform: translateX(0) !important; opacity: 0;" /> </div> diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts index d5e8c559326..b016a91d8a4 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.spec.ts @@ -155,8 +155,9 @@ describe("OverlayNotificationsContentService", () => { { command: "initNotificationBar", initData: expect.any(Object), + parentOrigin: expect.any(String), }, - "*", + overlayNotificationsContentService["extensionOrigin"], ); }); }); @@ -257,7 +258,7 @@ describe("OverlayNotificationsContentService", () => { expect(postMessageSpy).toHaveBeenCalledWith( { command: "saveCipherAttemptCompleted", error: undefined }, - "*", + overlayNotificationsContentService["extensionOrigin"], ); }); }); diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 0afa4f1409b..8dc08169468 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; +import { BrowserApi } from "../../../../platform/browser/browser-api"; import { NotificationBarIframeInitData, NotificationType, @@ -22,6 +23,7 @@ export class OverlayNotificationsContentService private notificationBarIframeElement: HTMLIFrameElement | null = null; private notificationBarShadowRoot: ShadowRoot | null = null; private currentNotificationBarType: NotificationType | null = null; + private readonly extensionOrigin: string; private notificationBarContainerStyles: Partial<CSSStyleDeclaration> = { height: "400px", width: "430px", @@ -61,6 +63,7 @@ export class OverlayNotificationsContentService }; constructor() { + this.extensionOrigin = BrowserApi.getRuntimeURL("")?.slice(0, -1); void sendExtensionMessage("checkNotificationQueue"); } @@ -181,7 +184,10 @@ export class OverlayNotificationsContentService this.currentNotificationBarType = initData.type; this.notificationBarIframeElement = globalThis.document.createElement("iframe"); this.notificationBarIframeElement.id = "bit-notification-bar-iframe"; - this.notificationBarIframeElement.src = chrome.runtime.getURL("notification/bar.html"); + const parentOrigin = globalThis.location.origin; + const iframeUrl = new URL(BrowserApi.getRuntimeURL("notification/bar.html")); + iframeUrl.searchParams.set("parentOrigin", parentOrigin); + this.notificationBarIframeElement.src = iframeUrl.toString(); setElementStyles( this.notificationBarIframeElement, { @@ -254,7 +260,11 @@ export class OverlayNotificationsContentService return; } - this.sendMessageToNotificationBarIframe({ command: "initNotificationBar", initData }); + this.sendMessageToNotificationBarIframe({ + command: "initNotificationBar", + initData, + parentOrigin: globalThis.location.origin, + }); globalThis.removeEventListener("message", handleInitNotificationBarMessage); }; @@ -303,7 +313,7 @@ export class OverlayNotificationsContentService */ private sendMessageToNotificationBarIframe(message: Record<string, any>) { if (this.notificationBarIframeElement) { - this.notificationBarIframeElement.contentWindow.postMessage(message, "*"); + this.notificationBarIframeElement.contentWindow.postMessage(message, this.extensionOrigin); } } diff --git a/apps/browser/src/autofill/spec/autofill-mocks.ts b/apps/browser/src/autofill/spec/autofill-mocks.ts index 3714ef2105b..423ba3dd0fe 100644 --- a/apps/browser/src/autofill/spec/autofill-mocks.ts +++ b/apps/browser/src/autofill/spec/autofill-mocks.ts @@ -175,6 +175,7 @@ export function createInitAutofillInlineMenuButtonMessageMock( styleSheetUrl: "https://jest-testing-website.com", authStatus: AuthenticationStatus.Unlocked, portKey: "portKey", + token: "test-token", ...customFields, }; } @@ -212,6 +213,7 @@ export function createInitAutofillInlineMenuListMessageMock( theme: ThemeTypes.Light, authStatus: AuthenticationStatus.Unlocked, portKey: "portKey", + token: "test-token", inlineMenuFillType: CipherType.Login, ciphers: [ createAutofillOverlayCipherDataMock(1, { diff --git a/apps/browser/src/autofill/spec/testing-utils.ts b/apps/browser/src/autofill/spec/testing-utils.ts index 0082f022fb6..20416413d25 100644 --- a/apps/browser/src/autofill/spec/testing-utils.ts +++ b/apps/browser/src/autofill/spec/testing-utils.ts @@ -1,5 +1,7 @@ import { mock } from "jest-mock-extended"; +import { BrowserApi } from "../../platform/browser/browser-api"; + export function triggerTestFailure() { expect(true).toBe("Test has failed."); } @@ -11,7 +13,11 @@ export function flushPromises() { }); } -export function postWindowMessage(data: any, origin = "https://localhost/", source = window) { +export function postWindowMessage( + data: any, + origin: string = BrowserApi.getRuntimeURL("")?.slice(0, -1), + source: Window | MessageEventSource | null = window, +) { globalThis.dispatchEvent(new MessageEvent("message", { data, origin, source })); } From 854f2abd2804cc635e8493d6c3c7182275127cc9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:49:25 +0100 Subject: [PATCH 241/249] [deps] Autofill: Update tldts to v7.0.18 (#17452) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 18 +++++++++--------- package.json | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index a7a7bfd0c7b..63aa8a2360b 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -88,7 +88,7 @@ "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "semver": "7.7.3", - "tldts": "7.0.1", + "tldts": "7.0.18", "zxcvbn": "4.4.2" } } diff --git a/package-lock.json b/package-lock.json index 1e2b8119a95..2a36e06e7a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "rxjs": "7.8.1", "semver": "7.7.3", "tabbable": "6.3.0", - "tldts": "7.0.1", + "tldts": "7.0.18", "ts-node": "10.9.2", "utf-8-validate": "6.0.5", "zone.js": "0.15.1", @@ -226,7 +226,7 @@ "proper-lockfile": "4.1.2", "rxjs": "7.8.1", "semver": "7.7.3", - "tldts": "7.0.1", + "tldts": "7.0.18", "zxcvbn": "4.4.2" }, "bin": { @@ -39263,21 +39263,21 @@ } }, "node_modules/tldts": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.1.tgz", - "integrity": "sha512-C3TdHZKykiDkxPIKUYCDWyYpcLQ8bDYvF/RGfH66UikQX3Kro7ij2/WGNYgp5EfxXB4+Tu5H728uAgYGNE1eaQ==", + "version": "7.0.18", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.18.tgz", + "integrity": "sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==", "license": "MIT", "dependencies": { - "tldts-core": "^7.0.1" + "tldts-core": "^7.0.18" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.9.tgz", - "integrity": "sha512-/FGY1+CryHsxF9SFiPZlMOcwQsfABkAvOJO5VEKE8TNifVEqgMF7+UVXHGhm1z4gPUfvVS/EYcwhiRU3vUa1ag==", + "version": "7.0.19", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.19.tgz", + "integrity": "sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==", "license": "MIT" }, "node_modules/tmp": { diff --git a/package.json b/package.json index 2a06b80f007..cf08fc5a70f 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "rxjs": "7.8.1", "semver": "7.7.3", "tabbable": "6.3.0", - "tldts": "7.0.1", + "tldts": "7.0.18", "ts-node": "10.9.2", "utf-8-validate": "6.0.5", "zone.js": "0.15.1", From 273f04c6a324bda033dc5d346cad26db8ff883ca Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:12:20 -0600 Subject: [PATCH 242/249] [PM-26513] Desktop Archive Upgrade (#16964) * always shows desktop archive filter regardless of the users premium status * include spec files in tsconfig * add upgrade path for desktop * combine duplicate class instances * remove optional chaining * update tests to avoid null assertions * add test files to the spec tsconfig * implement signal for premium badge component * remove badge template reference --- .../filters/status-filter.component.html | 9 +- .../filters/status-filter.component.spec.ts | 98 +++++++++++++++++++ .../filters/status-filter.component.ts | 43 +++++++- .../vault/vault-filter/vault-filter.module.ts | 3 +- .../src/vault/app/vault/vault-v2.component.ts | 7 +- apps/desktop/tsconfig.spec.json | 3 +- .../components/vault-filter.component.ts | 8 +- 7 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.spec.ts diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.html b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.html index 9cb98145cfe..8b064778444 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.html +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.html @@ -30,20 +30,23 @@ </span> </li> <li - class="filter-option" - *ngIf="!hideArchive" + class="filter-option tw-flex tw-items-center tw-gap-2 [&>span]:tw-w-min" [ngClass]="{ active: activeFilter.status === 'archive' }" + *ngIf="!hideArchive" > <span class="filter-buttons"> <button type="button" class="filter-button" - (click)="applyFilter('archive')" + (click)="handleArchiveFilter($event)" [attr.aria-pressed]="activeFilter.status === 'archive'" > <i class="bwi bwi-fw bwi-archive" aria-hidden="true"></i> {{ "archiveNoun" | i18n }} </button> </span> + @if (!(canArchive$ | async)) { + <app-premium-badge></app-premium-badge> + } </li> <li class="filter-option" diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.spec.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.spec.ts new file mode 100644 index 00000000000..ba785310a0a --- /dev/null +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.spec.ts @@ -0,0 +1,98 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; +import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { StatusFilterComponent } from "./status-filter.component"; + +describe("StatusFilterComponent", () => { + let component: StatusFilterComponent; + let fixture: ComponentFixture<StatusFilterComponent>; + let cipherArchiveService: jest.Mocked<CipherArchiveService>; + let accountService: FakeAccountService; + + const mockUserId = Utils.newGuid() as UserId; + const event = new Event("click"); + + beforeEach(async () => { + accountService = mockAccountServiceWith(mockUserId); + cipherArchiveService = mock<CipherArchiveService>(); + + await TestBed.configureTestingModule({ + declarations: [StatusFilterComponent], + providers: [ + { provide: AccountService, useValue: accountService }, + { provide: CipherArchiveService, useValue: cipherArchiveService }, + { provide: PremiumUpgradePromptService, useValue: mock<PremiumUpgradePromptService>() }, + { + provide: BillingAccountProfileStateService, + useValue: mock<BillingAccountProfileStateService>(), + }, + { provide: I18nService, useValue: { t: (key: string) => key } }, + ], + imports: [JslibModule, PremiumBadgeComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(StatusFilterComponent); + component = fixture.componentInstance; + component.activeFilter = new VaultFilter(); + fixture.detectChanges(); + }); + + describe("handleArchiveFilter", () => { + const applyFilter = jest.fn(); + let promptForPremiumSpy: jest.SpyInstance; + + beforeEach(() => { + applyFilter.mockClear(); + component["applyFilter"] = applyFilter; + + promptForPremiumSpy = jest.spyOn(component["premiumBadgeComponent"]()!, "promptForPremium"); + }); + + it("should apply archive filter when userCanArchive returns true", async () => { + cipherArchiveService.userCanArchive$.mockReturnValue(of(true)); + cipherArchiveService.archivedCiphers$.mockReturnValue(of([])); + + await component["handleArchiveFilter"](event); + + expect(applyFilter).toHaveBeenCalledWith("archive"); + expect(promptForPremiumSpy).not.toHaveBeenCalled(); + }); + + it("should apply archive filter when userCanArchive returns false but hasArchivedCiphers is true", async () => { + const mockCipher = new CipherView(); + mockCipher.id = "test-id"; + + cipherArchiveService.userCanArchive$.mockReturnValue(of(false)); + cipherArchiveService.archivedCiphers$.mockReturnValue(of([mockCipher])); + + await component["handleArchiveFilter"](event); + + expect(applyFilter).toHaveBeenCalledWith("archive"); + expect(promptForPremiumSpy).not.toHaveBeenCalled(); + }); + + it("should prompt for premium when userCanArchive returns false and hasArchivedCiphers is false", async () => { + cipherArchiveService.userCanArchive$.mockReturnValue(of(false)); + cipherArchiveService.archivedCiphers$.mockReturnValue(of([])); + + await component["handleArchiveFilter"](event); + + expect(applyFilter).not.toHaveBeenCalled(); + expect(promptForPremiumSpy).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index db546f76a2c..95ffd3f0212 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -1,6 +1,11 @@ -import { Component } from "@angular/core"; +import { Component, viewChild } from "@angular/core"; +import { combineLatest, firstValueFrom, map, switchMap } from "rxjs"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/angular/vault/vault-filter/components/status-filter.component"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @@ -9,4 +14,38 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a templateUrl: "status-filter.component.html", standalone: false, }) -export class StatusFilterComponent extends BaseStatusFilterComponent {} +export class StatusFilterComponent extends BaseStatusFilterComponent { + private readonly premiumBadgeComponent = viewChild(PremiumBadgeComponent); + + private userId$ = this.accountService.activeAccount$.pipe(getUserId); + protected canArchive$ = this.userId$.pipe( + switchMap((userId) => this.cipherArchiveService.userCanArchive$(userId)), + ); + + protected hasArchivedCiphers$ = this.userId$.pipe( + switchMap((userId) => + this.cipherArchiveService.archivedCiphers$(userId).pipe(map((ciphers) => ciphers.length > 0)), + ), + ); + + constructor( + private accountService: AccountService, + private cipherArchiveService: CipherArchiveService, + ) { + super(); + } + + protected async handleArchiveFilter(event: Event) { + const [canArchive, hasArchivedCiphers] = await firstValueFrom( + combineLatest([this.canArchive$, this.hasArchivedCiphers$]), + ); + + if (canArchive || hasArchivedCiphers) { + this.applyFilter("archive"); + } else if (this.premiumBadgeComponent()) { + // The `premiumBadgeComponent` should always be defined here, adding the + // if to satisfy TypeScript. + await this.premiumBadgeComponent().promptForPremium(event); + } + } +} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts index 8729996c835..54a6d33ca6a 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; +import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DeprecatedVaultFilterService as DeprecatedVaultFilterServiceAbstraction } from "@bitwarden/angular/vault/abstractions/deprecated-vault-filter.service"; import { VaultFilterService } from "@bitwarden/angular/vault/vault-filter/services/vault-filter.service"; @@ -13,7 +14,7 @@ import { TypeFilterComponent } from "./filters/type-filter.component"; import { VaultFilterComponent } from "./vault-filter.component"; @NgModule({ - imports: [CommonModule, JslibModule], + imports: [CommonModule, JslibModule, PremiumBadgeComponent], declarations: [ VaultFilterComponent, CollectionFilterComponent, diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index bdade04bacd..6c4ebe13f14 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -565,10 +565,15 @@ export class VaultV2Component<C extends CipherViewLike> } } - if (userCanArchive && !cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) { + if (!cipher.organizationId && !cipher.isDeleted && !cipher.isArchived) { menu.push({ label: this.i18nService.t("archiveVerb"), click: async () => { + if (!userCanArchive) { + await this.premiumUpgradePromptService.promptForPremium(); + return; + } + await this.archiveCipherUtilitiesService.archiveCipher(cipher); await this.refreshCurrentCipher(); }, diff --git a/apps/desktop/tsconfig.spec.json b/apps/desktop/tsconfig.spec.json index d52d889aa78..e6627a8ce45 100644 --- a/apps/desktop/tsconfig.spec.json +++ b/apps/desktop/tsconfig.spec.json @@ -4,5 +4,6 @@ "isolatedModules": true, "emitDecoratorMetadata": false }, - "files": ["./test.setup.ts"] + "files": ["./test.setup.ts"], + "include": ["src/**/*.spec.ts"] } diff --git a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts index 9b1d6286a9a..659db1bb925 100644 --- a/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts +++ b/libs/angular/src/vault/vault-filter/components/vault-filter.component.ts @@ -88,14 +88,10 @@ export class VaultFilterComponent implements OnInit { this.folders$ = await this.vaultFilterService.buildNestedFolders(); this.collections = await this.initCollections(); - const userCanArchive = await firstValueFrom( - this.cipherArchiveService.userCanArchive$(this.activeUserId), - ); - const showArchiveVault = await firstValueFrom( - this.cipherArchiveService.showArchiveVault$(this.activeUserId), + this.showArchiveVaultFilter = await firstValueFrom( + this.cipherArchiveService.hasArchiveFlagEnabled$(), ); - this.showArchiveVaultFilter = userCanArchive || showArchiveVault; this.isLoaded = true; } From 63812009d76896815e2fad699085344f850a0019 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:37:18 +1000 Subject: [PATCH 243/249] [PM-25913] Fix owners unable to rename provider-managed organization (#17482) Update to match new API: send null properties for organization properties that are unchanged --- .../organizations/settings/account.component.ts | 17 +++++------------ .../request/organization-update.request.ts | 11 ++++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 68b220aeac0..ec8ba59b987 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -168,18 +168,11 @@ export class AccountComponent implements OnInit, OnDestroy { return; } - const request = new OrganizationUpdateRequest(); - - /* - * When you disable a FormControl, it is removed from formGroup.values, so we have to use - * the original value. - * */ - request.name = this.formGroup.get("orgName").disabled - ? this.org.name - : this.formGroup.value.orgName; - request.billingEmail = this.formGroup.get("billingEmail").disabled - ? this.org.billingEmail - : this.formGroup.value.billingEmail; + // The server ignores any undefined values, so it's ok to reference disabled form fields here + const request: OrganizationUpdateRequest = { + name: this.formGroup.value.orgName, + billingEmail: this.formGroup.value.billingEmail, + }; // Backfill pub/priv key if necessary if (!this.org.hasPublicAndPrivateKeys) { diff --git a/libs/common/src/admin-console/models/request/organization-update.request.ts b/libs/common/src/admin-console/models/request/organization-update.request.ts index 1cde23dc675..0fa5dced880 100644 --- a/libs/common/src/admin-console/models/request/organization-update.request.ts +++ b/libs/common/src/admin-console/models/request/organization-update.request.ts @@ -1,10 +1,7 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { OrganizationKeysRequest } from "./organization-keys.request"; -export class OrganizationUpdateRequest { - name: string; - businessName: string; - billingEmail: string; - keys: OrganizationKeysRequest; +export interface OrganizationUpdateRequest { + name?: string; + billingEmail?: string; + keys?: OrganizationKeysRequest; } From 94327b8caa22db0e4959221497a1b712bd1e163f Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:59:09 -0800 Subject: [PATCH 244/249] [PM-28817] - update copy for vault premium spotlight (#17667) * update copy for vault premium spotlight * remove unecessary observable * fix logic --- apps/browser/src/_locales/en/messages.json | 8 ++++---- .../popup/components/vault-v2/vault-v2.component.html | 4 ++-- .../vault/popup/components/vault-v2/vault-v2.component.ts | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index fe979e129f4..dc25d90e6ce 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5821,11 +5821,11 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, - "upgradeCompleteSecurity": { - "message": "Upgrade for complete security" + "unlockAdvancedSecurity": { + "message": "Unlock advanced security features" }, - "premiumGivesMoreTools": { - "message": "Premium gives you more tools to stay secure, work efficiently, and stay in control." + "unlockAdvancedSecurityDesc": { + "message": "A Premium subscription gives you more tools to stay secure and in control" }, "explorePremium": { "message": "Explore Premium" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 7a5a99c8100..347c5fe6286 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -44,8 +44,8 @@ <ng-container slot="above-scroll-area"> <ng-container *ngIf="showPremiumSpotlight$ | async"> <bit-spotlight - [title]="'upgradeCompleteSecurity' | i18n" - [subtitle]="'premiumGivesMoreTools' | i18n" + [title]="'unlockAdvancedSecurity' | i18n" + [subtitle]="'unlockAdvancedSecurityDesc' | i18n" [buttonText]="'explorePremium' | i18n" (onButtonClick)="showPremiumDialog()" (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.PremiumUpgrade)" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 471e6e70601..9cee4f66b67 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -165,15 +165,14 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected showPremiumSpotlight$ = combineLatest([ this.showPremiumNudgeSpotlight$, - this.showEmptyVaultSpotlight$, this.showHasItemsVaultSpotlight$, this.hasPremium$, this.cipherCount$, this.accountAgeInDays$, ]).pipe( map( - ([showNudge, emptyVault, hasItems, hasPremium, count, age]) => - showNudge && !emptyVault && !hasItems && !hasPremium && count >= 5 && age >= 7, + ([showPremiumNudge, showHasItemsNudge, hasPremium, count, age]) => + showPremiumNudge && !showHasItemsNudge && !hasPremium && count >= 5 && age >= 7, ), shareReplay({ bufferSize: 1, refCount: true }), ); From dd8e3f963df7648f2d861c0717a0048bb82b4cc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 17:08:17 -0500 Subject: [PATCH 245/249] [deps]: Update actions/checkout action to v5.0.1 (#17537) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../workflows/alert-ddg-files-modified.yml | 2 +- .github/workflows/auto-branch-updater.yml | 2 +- .github/workflows/build-browser.yml | 12 +++++------ .github/workflows/build-cli.yml | 8 ++++---- .github/workflows/build-desktop.yml | 20 +++++++++---------- .github/workflows/build-web.yml | 8 ++++---- .github/workflows/chromatic.yml | 2 +- .github/workflows/crowdin-pull.yml | 2 +- .github/workflows/lint-crowdin-config.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/locales-lint.yml | 4 ++-- .github/workflows/nx.yml | 2 +- .github/workflows/publish-cli.yml | 6 +++--- .github/workflows/publish-desktop.yml | 6 +++--- .github/workflows/publish-web.yml | 4 ++-- .github/workflows/release-browser.yml | 4 ++-- .github/workflows/release-cli.yml | 2 +- .github/workflows/release-desktop.yml | 2 +- .github/workflows/release-web.yml | 2 +- .github/workflows/repository-management.yml | 4 ++-- .../workflows/sdk-breaking-change-check.yml | 2 +- .../workflows/test-browser-interactions.yml | 2 +- .github/workflows/test.yml | 8 ++++---- .github/workflows/version-auto-bump.yml | 2 +- 24 files changed, 56 insertions(+), 56 deletions(-) diff --git a/.github/workflows/alert-ddg-files-modified.yml b/.github/workflows/alert-ddg-files-modified.yml index 4acab6b1c62..90c055a97b8 100644 --- a/.github/workflows/alert-ddg-files-modified.yml +++ b/.github/workflows/alert-ddg-files-modified.yml @@ -14,7 +14,7 @@ jobs: pull-requests: write steps: - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/auto-branch-updater.yml b/.github/workflows/auto-branch-updater.yml index dcd031af0de..02176b3169e 100644 --- a/.github/workflows/auto-branch-updater.yml +++ b/.github/workflows/auto-branch-updater.yml @@ -30,7 +30,7 @@ jobs: run: echo "branch=${GITHUB_REF#refs/heads/}" >> "$GITHUB_OUTPUT" - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: 'eu-web-${{ steps.setup.outputs.branch }}' fetch-depth: 0 diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index 3990a8bef95..ab932c561ba 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -55,7 +55,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -94,7 +94,7 @@ jobs: working-directory: apps/browser steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -146,7 +146,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -254,7 +254,7 @@ jobs: artifact_name: "dist-opera-MV3" steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -386,7 +386,7 @@ jobs: _NODE_VERSION: ${{ needs.setup.outputs.node_version }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -542,7 +542,7 @@ jobs: - build-safari steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index babd00a323f..964cbc834c5 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -59,7 +59,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -114,7 +114,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -311,7 +311,7 @@ jobs: _WIN_PKG_VERSION: 3.5 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -520,7 +520,7 @@ jobs: _PACKAGE_VERSION: ${{ needs.setup.outputs.package_version }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index d3566535b65..87ea808a97b 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -88,7 +88,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: true @@ -173,7 +173,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -322,7 +322,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -470,7 +470,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -736,7 +736,7 @@ jobs: NODE_OPTIONS: --max_old_space_size=4096 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -979,7 +979,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1216,7 +1216,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1488,7 +1488,7 @@ jobs: working-directory: apps/desktop steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -1826,7 +1826,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index caf806af9f0..02ab7727c24 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -64,7 +64,7 @@ jobs: has_secrets: ${{ steps.check-secrets.outputs.has_secrets }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -144,7 +144,7 @@ jobs: _VERSION: ${{ needs.setup.outputs.version }} steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false @@ -174,7 +174,7 @@ jobs: echo "server_ref=$SERVER_REF" >> "$GITHUB_OUTPUT" - name: Check out Server repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: path: server repository: bitwarden/server @@ -367,7 +367,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} persist-credentials: false diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index aa0183ac16f..677d3dfc1df 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml index 311737a2c0e..5475c4dd692 100644 --- a/.github/workflows/crowdin-pull.yml +++ b/.github/workflows/crowdin-pull.yml @@ -58,7 +58,7 @@ jobs: permission-pull-requests: write # for generating pull requests - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: token: ${{ steps.app-token.outputs.token }} persist-credentials: false diff --git a/.github/workflows/lint-crowdin-config.yml b/.github/workflows/lint-crowdin-config.yml index 8d6bf254906..b0efeb50823 100644 --- a/.github/workflows/lint-crowdin-config.yml +++ b/.github/workflows/lint-crowdin-config.yml @@ -22,7 +22,7 @@ jobs: ] steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 1 persist-credentials: false diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 67186905390..48d3eca2f4e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -94,7 +94,7 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/locales-lint.yml b/.github/workflows/locales-lint.yml index da79f9aa21f..8335d6aacad 100644 --- a/.github/workflows/locales-lint.yml +++ b/.github/workflows/locales-lint.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - name: Checkout base branch repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ github.event.pull_request.base.sha }} path: base diff --git a/.github/workflows/nx.yml b/.github/workflows/nx.yml index 43361bc983d..0f01aa27899 100644 --- a/.github/workflows/nx.yml +++ b/.github/workflows/nx.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 426947526a4..8fcd1fe7c98 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -103,7 +103,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -151,7 +151,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -203,7 +203,7 @@ jobs: _PKG_VERSION: ${{ needs.setup.outputs.release_version }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/publish-desktop.yml b/.github/workflows/publish-desktop.yml index b17312950e9..3d512d49559 100644 --- a/.github/workflows/publish-desktop.yml +++ b/.github/workflows/publish-desktop.yml @@ -204,7 +204,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -258,7 +258,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout Repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -315,7 +315,7 @@ jobs: _RELEASE_TAG: ${{ needs.setup.outputs.tag_name }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml index 4f41898a9b2..be93ee61479 100644 --- a/.github/workflows/publish-web.yml +++ b/.github/workflows/publish-web.yml @@ -28,7 +28,7 @@ jobs: contents: read steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -74,7 +74,7 @@ jobs: echo "Github Release Option: $_RELEASE_OPTION" - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-browser.yml b/.github/workflows/release-browser.yml index 53382539b89..ff5fb669faf 100644 --- a/.github/workflows/release-browser.yml +++ b/.github/workflows/release-browser.yml @@ -28,7 +28,7 @@ jobs: release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -61,7 +61,7 @@ jobs: contents: read steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 4b94939b9dc..08045b8d3c7 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -29,7 +29,7 @@ jobs: release_version: ${{ steps.version.outputs.version }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index 10a0f581faa..c6b671ee5ea 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -31,7 +31,7 @@ jobs: release_channel: ${{ steps.release_channel.outputs.channel }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml index 9203769bc77..fc0ac340234 100644 --- a/.github/workflows/release-web.yml +++ b/.github/workflows/release-web.yml @@ -25,7 +25,7 @@ jobs: tag_version: ${{ steps.version.outputs.tag }} steps: - name: Checkout repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/repository-management.yml b/.github/workflows/repository-management.yml index 2a58e2fa828..faf119cce2b 100644 --- a/.github/workflows/repository-management.yml +++ b/.github/workflows/repository-management.yml @@ -104,7 +104,7 @@ jobs: private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} - name: Check out branch - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: main token: ${{ steps.app-token.outputs.token }} @@ -469,7 +469,7 @@ jobs: private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} - name: Check out target ref - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: ${{ inputs.target_ref }} token: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/sdk-breaking-change-check.yml b/.github/workflows/sdk-breaking-change-check.yml index 1b9653417f2..14547b3942f 100644 --- a/.github/workflows/sdk-breaking-change-check.yml +++ b/.github/workflows/sdk-breaking-change-check.yml @@ -64,7 +64,7 @@ jobs: uses: bitwarden/gh-actions/azure-logout@main - name: Check out clients repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/test-browser-interactions.yml b/.github/workflows/test-browser-interactions.yml index bc50a623172..dfc0f28b9c6 100644 --- a/.github/workflows/test-browser-interactions.yml +++ b/.github/workflows/test-browser-interactions.yml @@ -18,7 +18,7 @@ jobs: id-token: write steps: - name: Checkout code - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: fetch-depth: 0 persist-credentials: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f471826355f..f53bfc39d36 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -103,7 +103,7 @@ jobs: sudo apt-get install -y gnome-keyring dbus-x11 - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -137,7 +137,7 @@ jobs: runs-on: macos-14 steps: - name: Checkout - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -173,7 +173,7 @@ jobs: - rust-coverage steps: - name: Check out repo - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml index d807dd046d3..65f004149de 100644 --- a/.github/workflows/version-auto-bump.yml +++ b/.github/workflows/version-auto-bump.yml @@ -39,7 +39,7 @@ jobs: permission-contents: write # for committing and pushing to the current branch - name: Check out target ref - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: ref: main token: ${{ steps.app-token.outputs.token }} From d444143a651dc93172de8bbee3e88f4c2a22e82d Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 16:33:13 -0600 Subject: [PATCH 246/249] update translation key to use noun form of `archive` (#17500) --- apps/browser/src/vault/popup/settings/archive.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/settings/archive.component.html b/apps/browser/src/vault/popup/settings/archive.component.html index 059d636c60d..a7b23dc5122 100644 --- a/apps/browser/src/vault/popup/settings/archive.component.html +++ b/apps/browser/src/vault/popup/settings/archive.component.html @@ -1,5 +1,5 @@ <popup-page [loading]="loading$ | async"> - <popup-header slot="header" [pageTitle]="'archive' | i18n" showBackButton> + <popup-header slot="header" [pageTitle]="'archiveNoun' | i18n" showBackButton> <ng-container slot="end"> <app-pop-out></app-pop-out> </ng-container> From fdacd01f83d02237e69adfcfb00411b87a4feba5 Mon Sep 17 00:00:00 2001 From: Github Actions <actions@github.com> Date: Wed, 26 Nov 2025 01:15:04 +0000 Subject: [PATCH 247/249] Bumped Desktop client to 2025.11.3 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2633f3d5909..8ea17d96b91 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.11.2", + "version": "2025.11.3", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 0a4204bf233..d16be39369d 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.11.2", + "version": "2025.11.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.11.2", + "version": "2025.11.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 765bc771b9e..2f58b712a86 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.11.2", + "version": "2025.11.3", "author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 2a36e06e7a5..124bb8dcd36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -280,7 +280,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.11.2", + "version": "2025.11.3", "hasInstallScript": true, "license": "GPL-3.0" }, From f27ce4342c8c6d02b19a4071c63ed07633824ec3 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 25 Nov 2025 19:53:20 -0600 Subject: [PATCH 248/249] use `default-trailing` slot for attachments on the at-risk passwords page (#17203) --- .../at-risk-passwords.component.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html index 1ffb404fddb..3953d8f1eab 100644 --- a/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html +++ b/apps/browser/src/vault/popup/components/at-risk-passwords/at-risk-passwords.component.html @@ -53,12 +53,14 @@ <app-vault-icon [cipher]="cipher"></app-vault-icon> </div> <span data-testid="item-name">{{ cipher.name }}</span> - <i - *ngIf="cipher.hasAttachments" - class="bwi bwi-paperclip bwi-sm" - [appA11yTitle]="'attachments' | i18n" - ></i> <span slot="secondary">{{ cipher.subTitle }}</span> + <div slot="default-trailing"> + <i + *ngIf="cipher.hasAttachments" + class="bwi bwi-paperclip bwi-sm" + [appA11yTitle]="'attachments' | i18n" + ></i> + </div> </button> <bit-item-action slot="end"> <button From ab543b929d3e3f053d60762f1eeb77d01fca00d9 Mon Sep 17 00:00:00 2001 From: Oscar Hinton <Hinton@users.noreply.github.com> Date: Wed, 26 Nov 2025 10:52:08 +0100 Subject: [PATCH 249/249] [CL-932] Migrate banner component to OnPush & Signals (#17387) Migrates the BannerComponent to use computed signals and OnPush. --- .../src/banner/banner.component.html | 6 +- .../components/src/banner/banner.component.ts | 71 ++++++++++++------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/libs/components/src/banner/banner.component.html b/libs/components/src/banner/banner.component.html index bfde8135da9..19875d69f37 100644 --- a/libs/components/src/banner/banner.component.html +++ b/libs/components/src/banner/banner.component.html @@ -1,11 +1,11 @@ <div class="tw-flex tw-items-center tw-gap-4 tw-p-2 tw-ps-4 tw-text-main tw-border-transparent tw-bg-clip-padding tw-border-solid tw-border-b tw-border-0" - [ngClass]="bannerClass" + [class]="bannerClass()" [attr.role]="useAlertRole() ? 'status' : null" [attr.aria-live]="useAlertRole() ? 'polite' : null" > - @if (icon(); as icon) { - <i class="bwi tw-align-middle tw-text-base" [ngClass]="icon" aria-hidden="true"></i> + @if (displayIcon(); as icon) { + <i class="bwi tw-align-middle tw-text-base" [class]="icon" aria-hidden="true"></i> } <!-- Overriding focus-visible color for link buttons for a11y against colored background --> <span class="tw-grow tw-text-base [&>button[bitlink]:focus-visible:before]:!tw-ring-text-main"> diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index 1f9bf960d4b..2f36353cfcd 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -1,5 +1,4 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnInit, Output, EventEmitter, input, model } from "@angular/core"; +import { ChangeDetectionStrategy, Component, computed, input, output } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; @@ -13,47 +12,69 @@ const defaultIcon: Record<BannerType, string> = { warning: "bwi-exclamation-triangle", danger: "bwi-error", }; -/** - * Banners are used for important communication with the user that needs to be seen right away, but has - * little effect on the experience. Banners appear at the top of the user's screen on page load and - * persist across all pages a user navigates to. - * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. - * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. - * - Avoid stacking multiple banners. - * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. +/** + * Banners are used for important communication with the user that needs to be seen right away, but has + * little effect on the experience. Banners appear at the top of the user's screen on page load and + * persist across all pages a user navigates to. + * + * - They should always be dismissible and never use a timeout. If a user dismisses a banner, it should not reappear during that same active session. + * - Use banners sparingly, as they can feel intrusive to the user if they appear unexpectedly. Their effectiveness may decrease if too many are used. + * - Avoid stacking multiple banners. + * - Banners can contain a button or anchor that uses the `bitLink` directive with `linkType="secondary"`. */ -// 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-banner", templateUrl: "./banner.component.html", - imports: [CommonModule, IconButtonModule, I18nPipe], + imports: [IconButtonModule, I18nPipe], host: { // Account for bit-layout's padding class: "tw-flex tw-flex-col [bit-layout_&]:-tw-mx-8 [bit-layout_&]:-tw-my-6 [bit-layout_&]:tw-pb-6", }, + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class BannerComponent implements OnInit { +export class BannerComponent { + /** + * The type of banner, which determines its color scheme. + */ readonly bannerType = input<BannerType>("info"); - // passing `null` will remove the icon from element from the banner - readonly icon = model<string | null>(); + /** + * The icon to display. If not provided, a default icon based on bannerType will be used. Explicitly passing null will remove the icon. + */ + readonly icon = input<string | null>(); + + /** + * Whether to use ARIA alert role for screen readers. + */ readonly useAlertRole = input(true); + + /** + * Whether to show the close button. + */ readonly showClose = input(true); - // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals - // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref - @Output() onClose = new EventEmitter<void>(); + /** + * Emitted when the banner is closed via the close button. + */ + readonly onClose = output(); - ngOnInit(): void { - if (!this.icon() && this.icon() !== null) { - this.icon.set(defaultIcon[this.bannerType()]); + /** + * The computed icon to display, falling back to the default icon for the banner type. + * Returns null if icon is explicitly set to null (to hide the icon). + */ + protected readonly displayIcon = computed(() => { + // If icon is explicitly null, don't show any icon + if (this.icon() === null) { + return null; } - } - get bannerClass() { + // If icon is undefined, fall back to default icon + return this.icon() ?? defaultIcon[this.bannerType()]; + }); + + protected readonly bannerClass = computed(() => { switch (this.bannerType()) { case "danger": return "tw-bg-danger-100 tw-border-b-danger-700"; @@ -64,5 +85,5 @@ export class BannerComponent implements OnInit { case "warning": return "tw-bg-warning-100 tw-border-b-warning-700"; } - } + }); }
{{ credential.name }}{{ credential.name }} diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index ae8f84715db..842bd8c0064 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -123,7 +123,7 @@ function displayHandoffMessage(client: string) { ? localeService.t("thisWindowWillCloseIn5Seconds") : localeService.t("youMayCloseThisWindow"); - h1.className = "tw-font-semibold"; + h1.className = "tw-font-medium"; p.className = "tw-mb-4"; content.appendChild(h1); diff --git a/apps/web/src/connectors/webauthn-fallback.html b/apps/web/src/connectors/webauthn-fallback.html index 43da5b1a485..ef85ce6f351 100644 --- a/apps/web/src/connectors/webauthn-fallback.html +++ b/apps/web/src/connectors/webauthn-fallback.html @@ -115,7 +115,7 @@ diff --git a/apps/web/src/connectors/webauthn-mobile.html b/apps/web/src/connectors/webauthn-mobile.html index 06df8b012ab..0551d176eab 100644 --- a/apps/web/src/connectors/webauthn-mobile.html +++ b/apps/web/src/connectors/webauthn-mobile.html @@ -24,7 +24,7 @@ diff --git a/apps/web/src/connectors/webauthn.html b/apps/web/src/connectors/webauthn.html index 27f143f90d3..358e589b68f 100644 --- a/apps/web/src/connectors/webauthn.html +++ b/apps/web/src/connectors/webauthn.html @@ -9,7 +9,7 @@ diff --git a/libs/angular/src/auth/device-management/device-management-item-group.component.html b/libs/angular/src/auth/device-management/device-management-item-group.component.html index b6a3ea2d8f8..68081f20199 100644 --- a/libs/angular/src/auth/device-management/device-management-item-group.component.html +++ b/libs/angular/src/auth/device-management/device-management-item-group.component.html @@ -22,7 +22,7 @@
- {{ "firstLogin" | i18n }}: + {{ "firstLogin" | i18n }}: {{ device.firstLogin | date: "medium" }}
@@ -52,7 +52,7 @@ }
- {{ "firstLogin" | i18n }}: + {{ "firstLogin" | i18n }}: {{ device.firstLogin | date: "medium" }}
diff --git a/libs/angular/src/auth/environment-selector/environment-selector.component.html b/libs/angular/src/auth/environment-selector/environment-selector.component.html index f6484ea1e5f..72d7355c399 100644 --- a/libs/angular/src/auth/environment-selector/environment-selector.component.html +++ b/libs/angular/src/auth/environment-selector/environment-selector.component.html @@ -38,7 +38,7 @@
{{ "accessing" | i18n }}: - {{ "important" | i18n }} + {{ "important" | i18n }} {{ "masterPassImportant" | i18n }} {{ minPasswordLengthMsg }}. diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html index 38dc874cd0f..18a0db30904 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.html @@ -23,7 +23,7 @@ {{ "notificationSentDeviceComplete" | i18n }}

-
{{ "fingerprintPhraseHeader" | i18n }}
+
{{ "fingerprintPhraseHeader" | i18n }}
{{ fingerprintPhrase }}
+ diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 9401a88ab76..84e5c33d20d 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -94,7 +94,7 @@ (change)="dataSource.checkAllFilteredUsers($any($event.target).checked)" id="selectAll" /> - - Total: {{ totalCost | currency: "$" }} / + Total: {{ totalCost | currency: "$" }} / {{ getBillingCadenceLabel(activePlans.length > 0 ? activePlans[0] : null) | i18n }} diff --git a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html index 99e1c173c2a..62ac981664a 100644 --- a/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html +++ b/libs/angular/src/billing/components/premium-upgrade-dialog/premium-upgrade-dialog.component.html @@ -36,7 +36,7 @@
- {{ + {{ cardDetails.price.amount | currency: "$" }} diff --git a/libs/pricing/src/components/pricing-card/pricing-card.component.html b/libs/pricing/src/components/pricing-card/pricing-card.component.html index bc0ca68c5c3..7d25bca5368 100644 --- a/libs/pricing/src/components/pricing-card/pricing-card.component.html +++ b/libs/pricing/src/components/pricing-card/pricing-card.component.html @@ -22,7 +22,7 @@ @if (price(); as priceValue) {
- {{ + {{ priceValue.amount | currency: "$" }} From 87dceff0c8d28c0ee990c254285f1c7a63f8f0ec Mon Sep 17 00:00:00 2001 From: Maximilian Power Date: Thu, 6 Nov 2025 16:32:51 +0100 Subject: [PATCH 042/249] add CSV download buttons to at risk members and applications drawers (#17172) * add CSV download buttons to at risk members and applications drawers --------- Co-authored-by: Alex <55413326+AlexRubik@users.noreply.github.com> --- .../risk-insights.component.html | 18 +++++ .../risk-insights.component.ts | 70 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index 15ccd3241e4..2476d79552f 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -118,6 +118,15 @@ ) | i18n }} +
{{ "email" | i18n }} @@ -173,6 +182,15 @@ ) | i18n }} +
{{ "application" | i18n }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index cde5d5c8c66..0bcc7ba8a0d 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -2,7 +2,7 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, EMPTY } from "rxjs"; +import { combineLatest, EMPTY, firstValueFrom } from "rxjs"; import { map, tap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -13,7 +13,9 @@ import { } from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { AsyncActionsModule, @@ -23,6 +25,8 @@ import { DrawerHeaderComponent, TabsModule, } from "@bitwarden/components"; +import { ExportHelper } from "@bitwarden/vault-export-core"; +import { exportToCSV } from "@bitwarden/web-vault/app/dirt/reports/report-utils"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { AllActivityComponent } from "./activity/all-activity.component"; @@ -88,6 +92,8 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { private configService: ConfigService, protected dataService: RiskInsightsDataService, protected i18nService: I18nService, + private fileDownloadService: FileDownloadService, + private logService: LogService, ) { this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; @@ -207,4 +213,66 @@ export class RiskInsightsComponent implements OnInit, OnDestroy { "import", ]); }; + + /** + * downloads at risk members as CSV + */ + downloadAtRiskMembers = async () => { + try { + const drawerDetails = await firstValueFrom(this.dataService.drawerDetails$); + + // Validate drawer is open and showing the correct drawer type + if ( + !drawerDetails.open || + drawerDetails.activeDrawerType !== DrawerType.OrgAtRiskMembers || + !drawerDetails.atRiskMemberDetails || + drawerDetails.atRiskMemberDetails.length === 0 + ) { + return; + } + + this.fileDownloadService.download({ + fileName: ExportHelper.getFileName("at-risk-members"), + blobData: exportToCSV(drawerDetails.atRiskMemberDetails, { + email: this.i18nService.t("email"), + atRiskPasswordCount: this.i18nService.t("atRiskPasswords"), + }), + blobOptions: { type: "text/plain" }, + }); + } catch (error) { + // Log error for debugging + this.logService.error("Failed to download at-risk members", error); + } + }; + + /** + * downloads at risk applications as CSV + */ + downloadAtRiskApplications = async () => { + try { + const drawerDetails = await firstValueFrom(this.dataService.drawerDetails$); + + // Validate drawer is open and showing the correct drawer type + if ( + !drawerDetails.open || + drawerDetails.activeDrawerType !== DrawerType.OrgAtRiskApps || + !drawerDetails.atRiskAppDetails || + drawerDetails.atRiskAppDetails.length === 0 + ) { + return; + } + + this.fileDownloadService.download({ + fileName: ExportHelper.getFileName("at-risk-applications"), + blobData: exportToCSV(drawerDetails.atRiskAppDetails, { + applicationName: this.i18nService.t("application"), + atRiskPasswordCount: this.i18nService.t("atRiskPasswords"), + }), + blobOptions: { type: "text/plain" }, + }); + } catch (error) { + // Log error for debugging + this.logService.error("Failed to download at-risk applications", error); + } + }; } From 1cc08c59680d1efe98f447fa6477e65568cb6d40 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Thu, 6 Nov 2025 10:45:36 -0500 Subject: [PATCH 043/249] [PM-26984] Use medium instead of semibold or bold (#17189) --- .../shared/report-card/report-card.component.html | 2 +- .../assign-tasks-view.component.html | 4 ++-- .../review-applications-view.component.html | 8 ++++---- .../empty-state-card.component.html | 10 +++------- .../access-intelligence/risk-insights.component.html | 8 ++++---- .../integration-card/integration-card.component.html | 6 +++--- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html index a6ae7a246ac..f0318028e60 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.html @@ -12,7 +12,7 @@
-

{{ title }}

+

{{ title }}

{{ description }}

@if (requiresPremium) { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html index 875e86ed40b..859bc73905c 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/assign-tasks-view.component.html @@ -22,7 +22,7 @@ aria-hidden="true" >
- + {{ atRiskCriticalMembersCount() }} @@ -42,7 +42,7 @@ >
- + {{ criticalApplicationsCount() }} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html index 15d8160a55d..244cf2c5931 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/review-applications-view.component.html @@ -23,16 +23,16 @@ > -
+ {{ "application" | i18n }} + {{ "atRiskPasswords" | i18n }} + {{ "totalPasswords" | i18n }} + {{ "atRiskMembers" | i18n }}
-
-
- -
- {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @ - {{ i.amount | currency: "$" }} - {{ i.quantity * i.amount | currency: "$" }} /{{ i.interval | i18n }}