-
-
-
-
-
- {{ s.name }}
-
-
-
- {{ "disabled" | i18n }}
-
-
-
- {{ "password" | i18n }}
-
-
-
- {{ "maxAccessCountReached" | i18n }}
-
-
-
- {{ "expired" | i18n }}
-
-
-
- {{ "pendingDeletion" | i18n }}
-
+
+
+
+
+
+
+ {{ s.name }}
+
+
+
+ {{ "disabled" | i18n }}
+
+
+
+ {{ "password" | i18n }}
+
+
+
+ {{ "maxAccessCountReached" | i18n }}
+
+
+
+ {{ "expired" | i18n }}
+
+
+
+ {{ "pendingDeletion" | i18n }}
+
+
{{ s.deletionDate | date: "medium" }}
diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
index ff0c21e4525..ee4e0ec5233 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.html
@@ -21,7 +21,7 @@
-
+
{{ "cancel" | i18n }}
-
+
diff --git a/libs/importer/src/components/dialog/import-error-dialog.component.html b/libs/importer/src/components/dialog/import-error-dialog.component.html
index c1ad8b53932..da4cc1de421 100644
--- a/libs/importer/src/components/dialog/import-error-dialog.component.html
+++ b/libs/importer/src/components/dialog/import-error-dialog.component.html
@@ -21,9 +21,9 @@
-
+
{{ "ok" | i18n }}
-
+
diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
index 1ad42f95ec2..f379f466b4a 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
+++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.html
@@ -26,7 +26,7 @@
-
+
{{ (variant === "add" ? "add" : "save") | i18n }}
@@ -43,6 +43,6 @@
[appA11yTitle]="'deleteCustomField' | i18n: customFieldForm.value.label"
(click)="removeField()"
>
-
+
diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
index 4869714332c..cefd6305973 100644
--- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
+++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.html
@@ -12,7 +12,7 @@
-
+
-
+
From abb314a0e78e326bc248c43182cf7fba503d5516 Mon Sep 17 00:00:00 2001
From: Justin Baur <19896123+justindbaur@users.noreply.github.com>
Date: Tue, 25 Mar 2025 13:32:11 -0400
Subject: [PATCH 10/66] [PM-19432] Fix Multiple WS Connections (#13985)
* Test facilitation changes
* Fix multiple connections to SignalR
---
.../internal/signalr-connection.service.ts | 46 +++++++++++++------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/libs/common/src/platform/notifications/internal/signalr-connection.service.ts b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
index e5d210266c0..8bea98cb506 100644
--- a/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
+++ b/libs/common/src/platform/notifications/internal/signalr-connection.service.ts
@@ -23,6 +23,11 @@ export type ReceiveMessage = { type: "ReceiveMessage"; message: NotificationResp
export type SignalRNotification = Heartbeat | ReceiveMessage;
+export type TimeoutManager = {
+ setTimeout: (handler: TimerHandler, timeout: number) => number;
+ clearTimeout: (timeoutId: number) => void;
+};
+
class SignalRLogger implements ILogger {
constructor(private readonly logService: LogService) {}
@@ -51,11 +56,14 @@ export class SignalRConnectionService {
constructor(
private readonly apiService: ApiService,
private readonly logService: LogService,
+ private readonly hubConnectionBuilderFactory: () => HubConnectionBuilder = () =>
+ new HubConnectionBuilder(),
+ private readonly timeoutManager: TimeoutManager = globalThis,
) {}
connect$(userId: UserId, notificationsUrl: string) {
return new Observable((subsciber) => {
- const connection = new HubConnectionBuilder()
+ const connection = this.hubConnectionBuilderFactory()
.withUrl(notificationsUrl + "/hub", {
accessTokenFactory: () => this.apiService.getActiveBearerToken(),
skipNegotiation: true,
@@ -76,48 +84,60 @@ export class SignalRConnectionService {
let reconnectSubscription: Subscription | null = null;
// Create schedule reconnect function
- const scheduleReconnect = (): Subscription => {
+ const scheduleReconnect = () => {
if (
connection == null ||
connection.state !== HubConnectionState.Disconnected ||
(reconnectSubscription != null && !reconnectSubscription.closed)
) {
- return Subscription.EMPTY;
+ // Skip scheduling a new reconnect, either the connection isn't disconnected
+ // or an active reconnect is already scheduled.
+ return;
}
- const randomTime = this.random();
- const timeoutHandler = setTimeout(() => {
+ // If we've somehow gotten here while the subscriber is closed,
+ // we do not want to reconnect. So leave.
+ if (subsciber.closed) {
+ return;
+ }
+
+ const randomTime = this.randomReconnectTime();
+ const timeoutHandler = this.timeoutManager.setTimeout(() => {
connection
.start()
- .then(() => (reconnectSubscription = null))
+ .then(() => {
+ reconnectSubscription = null;
+ })
.catch(() => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
}, randomTime);
- return new Subscription(() => clearTimeout(timeoutHandler));
+ reconnectSubscription = new Subscription(() =>
+ this.timeoutManager.clearTimeout(timeoutHandler),
+ );
};
connection.onclose((error) => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
// Start connection
connection.start().catch(() => {
- reconnectSubscription = scheduleReconnect();
+ scheduleReconnect();
});
return () => {
+ // Cancel any possible scheduled reconnects
+ reconnectSubscription?.unsubscribe();
connection?.stop().catch((error) => {
this.logService.error("Error while stopping SignalR connection", error);
- // TODO: Does calling stop call `onclose`?
- reconnectSubscription?.unsubscribe();
});
};
});
}
- private random() {
+ private randomReconnectTime() {
return (
Math.floor(Math.random() * (MAX_RECONNECT_TIME - MIN_RECONNECT_TIME + 1)) + MIN_RECONNECT_TIME
);
From 15b2b46b85b16e3e477dc562ccac80e2a9f2bdab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Tue, 25 Mar 2025 17:08:30 -0400
Subject: [PATCH 11/66] [PM-18665] introduce metadata provider (#13744)
---
.../integration/integration-context.spec.ts | 15 +-
.../tools/integration/integration-metadata.ts | 4 +-
.../src/tools/log/disabled-semantic-logger.ts | 8 +-
.../tools/log/semantic-logger.abstraction.ts | 1 +
.../generator/core/src/data/integrations.ts | 8 +-
.../generator/core/src/integration/addy-io.ts | 3 +-
.../core/src/integration/duck-duck-go.ts | 3 +-
.../core/src/integration/fastmail.ts | 3 +-
.../core/src/integration/firefox-relay.ts | 3 +-
.../core/src/integration/forward-email.ts | 3 +-
.../core/src/integration/simple-login.ts | 3 +-
.../core/src/metadata/algorithm-metadata.ts | 16 +-
.../core/src/metadata/email/catchall.spec.ts | 6 +-
.../core/src/metadata/email/forwarder.ts | 79 +++-
.../src/metadata/email/plus-address.spec.ts | 6 +-
.../generator/core/src/metadata/index.ts | 18 +-
.../metadata/password/eff-word-list.spec.ts | 28 +-
.../metadata/password/random-password.spec.ts | 12 +-
.../metadata/username/eff-word-list.spec.ts | 6 +-
.../policies/available-algorithms-policy.ts | 32 +-
.../generator-metadata-provider.spec.ts | 438 ++++++++++++++++++
.../services/generator-metadata-provider.ts | 252 ++++++++++
.../credential-generator-configuration.ts | 4 +-
.../core/src/types/generator-type.ts | 8 +-
.../core/src/types/metadata-request.ts | 13 +
25 files changed, 910 insertions(+), 62 deletions(-)
create mode 100644 libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
create mode 100644 libs/tools/generator/core/src/services/generator-metadata-provider.ts
create mode 100644 libs/tools/generator/core/src/types/metadata-request.ts
diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts
index 42581c08dee..67a40afb337 100644
--- a/libs/common/src/tools/integration/integration-context.spec.ts
+++ b/libs/common/src/tools/integration/integration-context.spec.ts
@@ -1,6 +1,7 @@
import { mock } from "jest-mock-extended";
import { I18nService } from "../../platform/abstractions/i18n.service";
+import { VendorId } from "../extension";
import { IntegrationContext } from "./integration-context";
import { IntegrationId } from "./integration-id";
@@ -8,7 +9,7 @@ import { IntegrationMetadata } from "./integration-metadata";
const EXAMPLE_META = Object.freeze({
// arbitrary
- id: "simplelogin" as IntegrationId,
+ id: "simplelogin" as IntegrationId & VendorId,
name: "Example",
// arbitrary
extends: ["forwarder"],
@@ -34,7 +35,7 @@ describe("IntegrationContext", () => {
it("throws when the baseurl isn't defined in metadata", () => {
const noBaseUrl: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
selfHost: "maybe",
@@ -56,7 +57,7 @@ describe("IntegrationContext", () => {
it("ignores settings when selfhost is 'never'", () => {
const selfHostNever: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -71,7 +72,7 @@ describe("IntegrationContext", () => {
it("always reads the settings when selfhost is 'always'", () => {
const selfHostAlways: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -86,7 +87,7 @@ describe("IntegrationContext", () => {
it("fails when the settings are empty and selfhost is 'always'", () => {
const selfHostAlways: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -101,7 +102,7 @@ describe("IntegrationContext", () => {
it("reads from the metadata by default when selfhost is 'maybe'", () => {
const selfHostMaybe: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
@@ -117,7 +118,7 @@ describe("IntegrationContext", () => {
it("overrides the metadata when selfhost is 'maybe'", () => {
const selfHostMaybe: IntegrationMetadata = {
- id: "simplelogin" as IntegrationId, // arbitrary
+ id: "simplelogin" as IntegrationId & VendorId, // arbitrary
name: "Example",
extends: ["forwarder"], // arbitrary
baseUrl: "example.com",
diff --git a/libs/common/src/tools/integration/integration-metadata.ts b/libs/common/src/tools/integration/integration-metadata.ts
index e460aae828c..2073b16feb0 100644
--- a/libs/common/src/tools/integration/integration-metadata.ts
+++ b/libs/common/src/tools/integration/integration-metadata.ts
@@ -1,10 +1,12 @@
+import { VendorId } from "../extension";
+
import { ExtensionPointId } from "./extension-point-id";
import { IntegrationId } from "./integration-id";
/** The capabilities and descriptive content for an integration */
export type IntegrationMetadata = {
/** Uniquely identifies the integrator. */
- id: IntegrationId;
+ id: IntegrationId & VendorId;
/** Brand name of the integrator. */
name: string;
diff --git a/libs/common/src/tools/log/disabled-semantic-logger.ts b/libs/common/src/tools/log/disabled-semantic-logger.ts
index 054c3ed390b..21ea48bbe51 100644
--- a/libs/common/src/tools/log/disabled-semantic-logger.ts
+++ b/libs/common/src/tools/log/disabled-semantic-logger.ts
@@ -12,7 +12,11 @@ export class DisabledSemanticLogger implements SemanticLogger {
error(_content: Jsonify, _message?: string): void {}
- panic(_content: Jsonify, message?: string): never {
- throw new Error(message);
+ panic(content: Jsonify, message?: string): never {
+ if (typeof content === "string" && !message) {
+ throw new Error(content);
+ } else {
+ throw new Error(message);
+ }
}
}
diff --git a/libs/common/src/tools/log/semantic-logger.abstraction.ts b/libs/common/src/tools/log/semantic-logger.abstraction.ts
index 196d1f3f12c..51aaa917378 100644
--- a/libs/common/src/tools/log/semantic-logger.abstraction.ts
+++ b/libs/common/src/tools/log/semantic-logger.abstraction.ts
@@ -9,6 +9,7 @@ export interface SemanticLogger {
*/
debug(message: string): void;
+ // FIXME: replace Jsonify parameter with structural logging schema type
/** Logs the content at debug priority.
* Debug messages are used for diagnostics, and are typically disabled
* in production builds.
diff --git a/libs/tools/generator/core/src/data/integrations.ts b/libs/tools/generator/core/src/data/integrations.ts
index 21c883cae02..ffe4676fcd7 100644
--- a/libs/tools/generator/core/src/data/integrations.ts
+++ b/libs/tools/generator/core/src/data/integrations.ts
@@ -1,5 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
@@ -29,8 +30,11 @@ export const Integrations = Object.freeze({
const integrations = new Map(Object.values(Integrations).map((i) => [i.id, i]));
-export function getForwarderConfiguration(id: IntegrationId): ForwarderConfiguration {
- const maybeForwarder = integrations.get(id);
+export function getForwarderConfiguration(
+ id: IntegrationId | VendorId,
+): ForwarderConfiguration {
+ // these casts are for compatibility; `IntegrationId` is the old form of `VendorId`
+ const maybeForwarder = integrations.get(id as string as IntegrationId & VendorId);
if (maybeForwarder && "forwarder" in maybeForwarder) {
return maybeForwarder as ForwarderConfiguration;
diff --git a/libs/tools/generator/core/src/integration/addy-io.ts b/libs/tools/generator/core/src/integration/addy-io.ts
index d9f2b9f121d..631c5fdb510 100644
--- a/libs/tools/generator/core/src/integration/addy-io.ts
+++ b/libs/tools/generator/core/src/integration/addy-io.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -100,7 +101,7 @@ const forwarder = Object.freeze({
export const AddyIo = Object.freeze({
// integration
- id: "anonaddy" as IntegrationId,
+ id: "anonaddy" as IntegrationId & VendorId,
name: "Addy.io",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/integration/duck-duck-go.ts b/libs/tools/generator/core/src/integration/duck-duck-go.ts
index 0bcdd560503..d2bd6173a14 100644
--- a/libs/tools/generator/core/src/integration/duck-duck-go.ts
+++ b/libs/tools/generator/core/src/integration/duck-duck-go.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -89,7 +90,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const DuckDuckGo = Object.freeze({
- id: "duckduckgo" as IntegrationId,
+ id: "duckduckgo" as IntegrationId & VendorId,
name: "DuckDuckGo",
baseUrl: "https://quack.duckduckgo.com/api",
selfHost: "never",
diff --git a/libs/tools/generator/core/src/integration/fastmail.ts b/libs/tools/generator/core/src/integration/fastmail.ts
index 69b908badc9..bfde1aa70f5 100644
--- a/libs/tools/generator/core/src/integration/fastmail.ts
+++ b/libs/tools/generator/core/src/integration/fastmail.ts
@@ -5,6 +5,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -159,7 +160,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const Fastmail = Object.freeze({
- id: "fastmail" as IntegrationId,
+ id: "fastmail" as IntegrationId & VendorId,
name: "Fastmail",
baseUrl: "https://api.fastmail.com",
selfHost: "maybe",
diff --git a/libs/tools/generator/core/src/integration/firefox-relay.ts b/libs/tools/generator/core/src/integration/firefox-relay.ts
index ae65611905f..9f40a3631ff 100644
--- a/libs/tools/generator/core/src/integration/firefox-relay.ts
+++ b/libs/tools/generator/core/src/integration/firefox-relay.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -97,7 +98,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const FirefoxRelay = Object.freeze({
- id: "firefoxrelay" as IntegrationId,
+ id: "firefoxrelay" as IntegrationId & VendorId,
name: "Firefox Relay",
baseUrl: "https://relay.firefox.com/api",
selfHost: "never",
diff --git a/libs/tools/generator/core/src/integration/forward-email.ts b/libs/tools/generator/core/src/integration/forward-email.ts
index d67b8d588bf..34b4602b94b 100644
--- a/libs/tools/generator/core/src/integration/forward-email.ts
+++ b/libs/tools/generator/core/src/integration/forward-email.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import { ApiSettings, IntegrationRequest } from "@bitwarden/common/tools/integration/rpc";
import { PrivateClassifier } from "@bitwarden/common/tools/private-classifier";
@@ -101,7 +102,7 @@ const forwarder = Object.freeze({
export const ForwardEmail = Object.freeze({
// integration metadata
- id: "forwardemail" as IntegrationId,
+ id: "forwardemail" as IntegrationId & VendorId,
name: "Forward Email",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/integration/simple-login.ts b/libs/tools/generator/core/src/integration/simple-login.ts
index 1581f3861f5..efbac69cec2 100644
--- a/libs/tools/generator/core/src/integration/simple-login.ts
+++ b/libs/tools/generator/core/src/integration/simple-login.ts
@@ -3,6 +3,7 @@ import {
GENERATOR_MEMORY,
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationContext, IntegrationId } from "@bitwarden/common/tools/integration";
import {
ApiSettings,
@@ -103,7 +104,7 @@ const forwarder = Object.freeze({
// integration-wide configuration
export const SimpleLogin = Object.freeze({
- id: "simplelogin" as IntegrationId,
+ id: "simplelogin" as IntegrationId & VendorId,
name: "SimpleLogin",
selfHost: "maybe",
extends: ["forwarder"],
diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
index f776dd76e54..c07deef5535 100644
--- a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
+++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts
@@ -1,5 +1,7 @@
import { CredentialAlgorithm, CredentialType } from "./type";
+type I18nKeyOrLiteral = string | { literal: string };
+
/** Credential generator metadata common across credential generators */
export type AlgorithmMetadata = {
/** Uniquely identifies the credential configuration
@@ -23,25 +25,25 @@ export type AlgorithmMetadata = {
/** Localization keys */
i18nKeys: {
/** descriptive name of the algorithm */
- name: string;
+ name: I18nKeyOrLiteral;
/** explanatory text for the algorithm */
- description?: string;
+ description?: I18nKeyOrLiteral;
/** labels the generate action */
- generateCredential: string;
+ generateCredential: I18nKeyOrLiteral;
/** message informing users when the generator produces a new credential */
- credentialGenerated: string;
+ credentialGenerated: I18nKeyOrLiteral;
/* labels the action that assigns a generated value to a domain object */
- useCredential: string;
+ useCredential: I18nKeyOrLiteral;
/** labels the generated output */
- credentialType: string;
+ credentialType: I18nKeyOrLiteral;
/** labels the copy output action */
- copyCredential: string;
+ copyCredential: I18nKeyOrLiteral;
};
/** fine-tunings for generator user experiences */
diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
index f63f141842c..d6cc1795e0b 100644
--- a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
+++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts
@@ -19,11 +19,13 @@ describe("email - catchall generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = catchall.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts
index 1dfc219d466..f4f150f33fa 100644
--- a/libs/tools/generator/core/src/metadata/email/forwarder.ts
+++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts
@@ -1,4 +1,75 @@
-// Forwarders are pending integration with the extension API
-//
-// They use the 300-block of weights and derive their metadata
-// using logic similar to `toCredentialGeneratorConfiguration`
+import { ExtensionMetadata, ExtensionStorageKey } from "@bitwarden/common/tools/extension/type";
+import { SelfHostedApiSettings } from "@bitwarden/common/tools/integration/rpc";
+import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
+
+import { getForwarderConfiguration } from "../../data";
+import { EmailDomainSettings, EmailPrefixSettings } from "../../engine";
+import { Forwarder } from "../../engine/forwarder";
+import { GeneratorDependencyProvider } from "../../types";
+import { Profile, Type } from "../data";
+import { GeneratorMetadata } from "../generator-metadata";
+import { ForwarderProfileMetadata } from "../profile-metadata";
+
+// These options are used by all forwarders; each forwarder uses a different set,
+// as defined by `GeneratorMetadata.capabilities.fields`.
+type ForwarderOptions = Partial;
+
+// update the extension metadata
+export function toForwarderMetadata(
+ extension: ExtensionMetadata,
+): GeneratorMetadata {
+ if (extension.site.id !== "forwarder") {
+ throw new Error(
+ `expected forwarder extension; received ${extension.site.id} (${extension.product.vendor.id})`,
+ );
+ }
+
+ const name = { literal: extension.product.name ?? extension.product.vendor.name };
+
+ const generator: GeneratorMetadata = {
+ id: { forwarder: extension.product.vendor.id },
+ category: Type.email,
+ weight: 300,
+ i18nKeys: {
+ name,
+ description: "forwardedEmailDesc",
+ generateCredential: "generateEmail",
+ credentialGenerated: "emailGenerated",
+ useCredential: "useThisEmail",
+ credentialType: "email",
+ copyCredential: "copyEmail",
+ },
+ capabilities: {
+ autogenerate: false,
+ fields: [...extension.requestedFields],
+ },
+ engine: {
+ create(dependencies: GeneratorDependencyProvider) {
+ const config = getForwarderConfiguration(extension.product.vendor.id);
+ return new Forwarder(config, dependencies.client, dependencies.i18nService);
+ },
+ },
+ profiles: {
+ [Profile.account]: {
+ type: "extension",
+ site: "forwarder",
+ storage: {
+ key: "forwarder",
+ frame: 512,
+ options: {
+ deserializer: (value) => value,
+ clearOn: ["logout"],
+ },
+ } satisfies ExtensionStorageKey,
+ constraints: {
+ default: {},
+ create() {
+ return new IdentityConstraint();
+ },
+ },
+ } satisfies ForwarderProfileMetadata,
+ },
+ };
+
+ return generator;
+}
diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
index 2ac7645ed30..063cb71c23a 100644
--- a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
+++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts
@@ -19,11 +19,13 @@ describe("email - plus address generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = plusAddress.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/metadata/index.ts b/libs/tools/generator/core/src/metadata/index.ts
index 79806fd1bcc..d9437822270 100644
--- a/libs/tools/generator/core/src/metadata/index.ts
+++ b/libs/tools/generator/core/src/metadata/index.ts
@@ -1,12 +1,24 @@
-import { AlgorithmsByType as ABT } from "./data";
+import {
+ Algorithm as AlgorithmData,
+ AlgorithmsByType as AlgorithmsByTypeData,
+ Type as TypeData,
+} from "./data";
import { CredentialType, CredentialAlgorithm } from "./type";
// `CredentialAlgorithm` is defined in terms of `ABT`; supplying
// type information in the barrel file breaks a circular dependency.
/** Credential generation algorithms grouped by purpose. */
-export const AlgorithmsByType: Record> = ABT;
+export const AlgorithmsByType: Record<
+ CredentialType,
+ ReadonlyArray
+> = AlgorithmsByTypeData;
+export const Algorithms: ReadonlyArray = Object.freeze(
+ Object.values(AlgorithmData),
+);
+export const Types: ReadonlyArray = Object.freeze(Object.values(TypeData));
-export { Profile, Type } from "./data";
+export { Profile, Type, Algorithm } from "./data";
+export { toForwarderMetadata } from "./email/forwarder";
export { GeneratorMetadata } from "./generator-metadata";
export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata";
export { GeneratorProfile, CredentialAlgorithm, CredentialType } from "./type";
diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
index 57961a60033..e02d63d3d59 100644
--- a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
+++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts
@@ -22,19 +22,21 @@ describe("password - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata | null = null;
beforeEach(() => {
const profile = effPassphrase.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ accountProfile = null;
}
});
describe("storage.options.deserializer", () => {
it("returns its input", () => {
- const value: PassphraseGenerationOptions = { ...accountProfile.storage.initial };
+ const value: PassphraseGenerationOptions = { ...accountProfile!.storage.initial };
- const result = accountProfile.storage.options.deserializer(value);
+ const result = accountProfile!.storage.options.deserializer(value);
expect(result).toBe(value);
});
@@ -46,15 +48,15 @@ describe("password - eff words generator metadata", () => {
// enclosed behaviors change.
it("creates a passphrase policy constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
- const constraints = accountProfile.constraints.create([], context);
+ const constraints = accountProfile!.constraints.create([], context);
expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints);
});
it("forwards the policy to the constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -66,13 +68,13 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
- const constraints = accountProfile.constraints.create(policies, context);
+ const constraints = accountProfile!.constraints.create(policies, context);
- expect(constraints.constraints.numWords.min).toEqual(6);
+ expect(constraints.constraints.numWords?.min).toEqual(6);
});
it("combines multiple policies in the constraints", () => {
- const context = { defaultConstraints: accountProfile.constraints.default };
+ const context = { defaultConstraints: accountProfile!.constraints.default };
const policies = [
{
type: PolicyType.PasswordGenerator,
@@ -92,10 +94,10 @@ describe("password - eff words generator metadata", () => {
},
] as Policy[];
- const constraints = accountProfile.constraints.create(policies, context);
+ const constraints = accountProfile!.constraints.create(policies, context);
- expect(constraints.constraints.numWords.min).toEqual(6);
- expect(constraints.constraints.capitalize.requiredValue).toEqual(true);
+ expect(constraints.constraints.numWords?.min).toEqual(6);
+ expect(constraints.constraints.capitalize?.requiredValue).toEqual(true);
});
});
});
diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
index d91ceaac248..9e38c50ee2a 100644
--- a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
+++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts
@@ -22,11 +22,13 @@ describe("password - characters generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = password.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
@@ -69,7 +71,7 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
- expect(constraints.constraints.length.min).toEqual(10);
+ expect(constraints.constraints.length?.min).toEqual(10);
});
it("combines multiple policies in the constraints", () => {
@@ -97,8 +99,8 @@ describe("password - characters generator metadata", () => {
const constraints = accountProfile.constraints.create(policies, context);
- expect(constraints.constraints.length.min).toEqual(14);
- expect(constraints.constraints.special.requiredValue).toEqual(true);
+ expect(constraints.constraints.length?.min).toEqual(14);
+ expect(constraints.constraints.special?.requiredValue).toEqual(true);
});
});
});
diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
index aba9680a448..d47d5ec9fcb 100644
--- a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
+++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts
@@ -20,11 +20,13 @@ describe("username - eff words generator metadata", () => {
});
describe("profiles[account]", () => {
- let accountProfile: CoreProfileMetadata = null;
+ let accountProfile: CoreProfileMetadata = null!;
beforeEach(() => {
const profile = effWordList.profiles[Profile.account];
- if (isCoreProfile(profile)) {
+ if (isCoreProfile(profile!)) {
accountProfile = profile;
+ } else {
+ throw new Error("this branch should never run");
}
});
diff --git a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
index f37a8b21a3f..0c44a1a0408 100644
--- a/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
+++ b/libs/tools/generator/core/src/policies/available-algorithms-policy.ts
@@ -5,13 +5,41 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
// implement ADR-0002
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
-import { CredentialAlgorithm, EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "..";
+import {
+ CredentialAlgorithm as LegacyAlgorithm,
+ EmailAlgorithms,
+ PasswordAlgorithms,
+ UsernameAlgorithms,
+} from "..";
+import { CredentialAlgorithm } from "../metadata";
/** Reduces policies to a set of available algorithms
* @param policies the policies to reduce
* @returns the resulting `AlgorithmAvailabilityPolicy`
*/
-export function availableAlgorithms(policies: Policy[]): CredentialAlgorithm[] {
+export function availableAlgorithms(policies: Policy[]): LegacyAlgorithm[] {
+ const overridePassword = policies
+ .filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
+ .reduce(
+ (type, policy) => (type === "password" ? type : (policy.data.overridePasswordType ?? type)),
+ null as LegacyAlgorithm,
+ );
+
+ const policy: LegacyAlgorithm[] = [...EmailAlgorithms, ...UsernameAlgorithms];
+ if (overridePassword) {
+ policy.push(overridePassword);
+ } else {
+ policy.push(...PasswordAlgorithms);
+ }
+
+ return policy;
+}
+
+/** Reduces policies to a set of available algorithms
+ * @param policies the policies to reduce
+ * @returns the resulting `AlgorithmAvailabilityPolicy`
+ */
+export function availableAlgorithms_vNext(policies: Policy[]): CredentialAlgorithm[] {
const overridePassword = policies
.filter((policy) => policy.type === PolicyType.PasswordGenerator && policy.enabled)
.reduce(
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
new file mode 100644
index 00000000000..958e5608449
--- /dev/null
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
@@ -0,0 +1,438 @@
+import { mock } from "jest-mock-extended";
+import { BehaviorSubject, ReplaySubject, firstValueFrom } from "rxjs";
+
+import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
+import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
+import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { LegacyEncryptorProvider } from "@bitwarden/common/tools/cryptography/legacy-encryptor-provider";
+import { UserEncryptor } from "@bitwarden/common/tools/cryptography/user-encryptor.abstraction";
+import {
+ ExtensionMetadata,
+ ExtensionSite,
+ Site,
+ SiteId,
+ SiteMetadata,
+} from "@bitwarden/common/tools/extension";
+import { ExtensionService } from "@bitwarden/common/tools/extension/extension.service";
+import { Bitwarden } from "@bitwarden/common/tools/extension/vendor/bitwarden";
+import { disabledSemanticLoggerProvider } from "@bitwarden/common/tools/log";
+import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
+import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
+import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
+import { deepFreeze } from "@bitwarden/common/tools/util";
+import { UserId } from "@bitwarden/common/types/guid";
+
+import { FakeAccountService, FakeStateProvider } from "../../../../../common/spec";
+import { Algorithm, AlgorithmsByType, CredentialAlgorithm, Type, Types } from "../metadata";
+import catchall from "../metadata/email/catchall";
+import plusAddress from "../metadata/email/plus-address";
+import passphrase from "../metadata/password/eff-word-list";
+import password from "../metadata/password/random-password";
+import effWordList from "../metadata/username/eff-word-list";
+import { CredentialPreference } from "../types";
+
+import { PREFERENCES } from "./credential-preferences";
+import { GeneratorMetadataProvider } from "./generator-metadata-provider";
+
+const SomeUser = "some user" as UserId;
+const SomeAccount = {
+ id: SomeUser,
+ email: "someone@example.com",
+ emailVerified: true,
+ name: "Someone",
+};
+const SomeAccount$ = new BehaviorSubject(SomeAccount);
+
+const SomeEncryptor: UserEncryptor = {
+ userId: SomeUser,
+
+ encrypt(secret) {
+ const tmp: any = secret;
+ return Promise.resolve({ foo: `encrypt(${tmp.foo})` } as any);
+ },
+
+ decrypt(secret) {
+ const tmp: any = JSON.parse(secret.encryptedString!);
+ return Promise.resolve({ foo: `decrypt(${tmp.foo})` } as any);
+ },
+};
+
+const SomeAccountService = new FakeAccountService({
+ [SomeUser]: SomeAccount,
+});
+
+const SomeStateProvider = new FakeStateProvider(SomeAccountService);
+
+const SystemProvider = {
+ encryptor: {
+ userEncryptor$: () => {
+ return new BehaviorSubject({ encryptor: SomeEncryptor, userId: SomeUser }).asObservable();
+ },
+ organizationEncryptor$() {
+ throw new Error("`organizationEncryptor$` should never be invoked.");
+ },
+ } as LegacyEncryptorProvider,
+ state: SomeStateProvider,
+ log: disabledSemanticLoggerProvider,
+} as UserStateSubjectDependencyProvider;
+
+const SomeSiteId: SiteId = Site.forwarder;
+
+const SomeSite: SiteMetadata = Object.freeze({
+ id: SomeSiteId,
+ availableFields: [],
+});
+
+const SomePolicyService = mock();
+
+const SomeExtensionService = mock();
+
+const ApplicationProvider = {
+ /** Policy configured by the administrative console */
+ policy: SomePolicyService,
+
+ /** Client extension metadata and profile access */
+ extension: SomeExtensionService,
+
+ /** Event monitoring and diagnostic interfaces */
+ log: disabledSemanticLoggerProvider,
+} as SystemServiceProvider;
+
+describe("GeneratorMetadataProvider", () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ SomeExtensionService.site.mockImplementation(() => new ExtensionSite(SomeSite, new Map()));
+ });
+
+ describe("constructor", () => {
+ it("throws when the forwarder site isn't defined by the extension service", () => {
+ SomeExtensionService.site.mockReturnValue(undefined);
+ expect(() => new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [])).toThrow(
+ "forwarder extension site not found",
+ );
+ });
+ });
+
+ describe("metadata", () => {
+ it("returns algorithm metadata", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ password,
+ ]);
+
+ const metadata = provider.metadata(password.id);
+
+ expect(metadata).toEqual(password);
+ });
+
+ it("returns forwarder metadata", async () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const metadata = provider.metadata({ forwarder: Bitwarden.id });
+
+ expect(metadata.id).toEqual({ forwarder: Bitwarden.id });
+ });
+
+ it("panics when metadata not found", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.metadata("not found" as any)).toThrow("metadata not found");
+ });
+
+ it("panics when an extension not found", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.metadata({ forwarder: "not found" as any })).toThrow(
+ "extension not found",
+ );
+ });
+ });
+
+ describe("types", () => {
+ it("returns the credential types", async () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.types();
+
+ expect(result).toEqual(expect.arrayContaining(Types));
+ });
+ });
+
+ describe("algorithms", () => {
+ it("returns the password category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.password });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.password]));
+ });
+
+ it("returns the username category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.username });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.username]));
+ });
+
+ it("returns the email category's algorithms", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ type: Type.email });
+
+ expect(result).toEqual(expect.arrayContaining(AlgorithmsByType[Type.email]));
+ });
+
+ it("includes forwarder vendors in the email category's algorithms", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const result = provider.algorithms({ type: Type.email });
+
+ expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]));
+ });
+
+ it.each([
+ [Algorithm.catchall],
+ [Algorithm.passphrase],
+ [Algorithm.password],
+ [Algorithm.plusAddress],
+ [Algorithm.username],
+ ])("returns explicit algorithms (=%p)", (algorithm) => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const result = provider.algorithms({ algorithm });
+
+ expect(result).toEqual([algorithm]);
+ });
+
+ it("returns explicit forwarders", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ const result = provider.algorithms({ algorithm: { forwarder: Bitwarden.id } });
+
+ expect(result).toEqual(expect.arrayContaining([{ forwarder: Bitwarden.id }]));
+ });
+
+ it("returns an empty array when the algorithm is invalid", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ // `any` cast required because this test subverts the type system
+ const result = provider.algorithms({ algorithm: "an invalid algorithm" as any });
+
+ expect(result).toEqual([]);
+ });
+
+ it("returns an empty array when the forwarder is invalid", () => {
+ const extensionMetadata: ExtensionMetadata = {
+ site: SomeSite,
+ product: { vendor: Bitwarden },
+ host: { authentication: true, selfHost: "maybe", baseUrl: "https://www.example.com" },
+ requestedFields: [],
+ };
+ const application = {
+ ...ApplicationProvider,
+ extension: mock({
+ site: () => new ExtensionSite(SomeSite, new Map([[Bitwarden.id, extensionMetadata]])),
+ }),
+ };
+ const provider = new GeneratorMetadataProvider(SystemProvider, application, []);
+
+ // `any` cast required because this test subverts the type system
+ const result = provider.algorithms({
+ algorithm: { forwarder: "an invalid forwarder" as any },
+ });
+
+ expect(result).toEqual([]);
+ });
+
+ it("panics when neither an algorithm nor a category is specified", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ // `any` cast required because this test subverts the type system
+ expect(() => provider.algorithms({} as any)).toThrow("algorithm or type required");
+ });
+ });
+
+ describe("algorithms$", () => {
+ it.each([
+ [Algorithm.catchall, catchall],
+ [Algorithm.username, effWordList],
+ [Algorithm.password, password],
+ ])("gets a specific algorithm", async (algorithm, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ metadata,
+ ]);
+ const result = new ReplaySubject(1);
+
+ provider.algorithms$({ algorithm }, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual([algorithm]);
+ });
+
+ it.each([
+ [Type.email, [catchall, plusAddress]],
+ [Type.username, [effWordList]],
+ [Type.password, [password, passphrase]],
+ ])("gets a category of algorithms", async (category, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
+ const result = new ReplaySubject(1);
+
+ provider.algorithms$({ type: category }, { account$: SomeAccount$ }).subscribe(result);
+
+ const expectedAlgorithms = expect.arrayContaining(metadata.map((m) => m.id));
+ await expect(firstValueFrom(result)).resolves.toEqual(expectedAlgorithms);
+ });
+
+ it("omits algorithms blocked by policy", async () => {
+ const policy = new Policy({
+ type: PolicyType.PasswordGenerator,
+ enabled: true,
+ data: {
+ overridePasswordType: Algorithm.password,
+ },
+ } as any);
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([policy]));
+ const metadata = [password, passphrase];
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
+ const algorithmResult = new ReplaySubject(1);
+ const categoryResult = new ReplaySubject(1);
+
+ provider
+ .algorithms$({ algorithm: Algorithm.passphrase }, { account$: SomeAccount$ })
+ .subscribe(algorithmResult);
+ provider
+ .algorithms$({ type: Type.password }, { account$: SomeAccount$ })
+ .subscribe(categoryResult);
+
+ await expect(firstValueFrom(algorithmResult)).resolves.toEqual([]);
+ await expect(firstValueFrom(categoryResult)).resolves.toEqual([password.id]);
+ });
+
+ it("omits algorithms whose metadata is unavailable", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ password,
+ ]);
+ const algorithmResult = new ReplaySubject(1);
+ const categoryResult = new ReplaySubject(1);
+
+ provider
+ .algorithms$({ algorithm: Algorithm.passphrase }, { account$: SomeAccount$ })
+ .subscribe(algorithmResult);
+ provider
+ .algorithms$({ type: Type.password }, { account$: SomeAccount$ })
+ .subscribe(categoryResult);
+
+ await expect(firstValueFrom(algorithmResult)).resolves.toEqual([]);
+ await expect(firstValueFrom(categoryResult)).resolves.toEqual([password.id]);
+ });
+
+ it("panics when neither algorithm nor category are specified", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ expect(() => provider.algorithms$({} as any, { account$: SomeAccount$ })).toThrow(
+ "algorithm or type required",
+ );
+ });
+ });
+
+ describe("preference$", () => {
+ const preferences: CredentialPreference = deepFreeze({
+ [Type.email]: { algorithm: Algorithm.catchall, updated: new Date() },
+ [Type.username]: { algorithm: Algorithm.username, updated: new Date() },
+ [Type.password]: { algorithm: Algorithm.password, updated: new Date() },
+ });
+ beforeEach(async () => {
+ await SomeStateProvider.setUserState(PREFERENCES, preferences, SomeAccount.id);
+ });
+
+ it.each([
+ [Type.email, catchall],
+ [Type.username, effWordList],
+ [Type.password, password],
+ ])("emits the user's %s preference", async (type, metadata) => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ metadata,
+ ]);
+ const result = new ReplaySubject(1);
+
+ provider.preference$(type, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual(preferences[type].algorithm);
+ });
+
+ it("emits a default when the user's preference is unavailable", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
+ plusAddress,
+ ]);
+ const result = new ReplaySubject(1);
+
+ // precondition: the preferred email is excluded from the provided metadata
+ expect(preferences.email.algorithm).not.toEqual(plusAddress.id);
+
+ provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toEqual(plusAddress.id);
+ });
+
+ it("emits undefined when the user's preference is unavailable and there is no metadata", async () => {
+ SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+ const result = new ReplaySubject(1);
+
+ provider.preference$(Type.email, { account$: SomeAccount$ }).subscribe(result);
+
+ await expect(firstValueFrom(result)).resolves.toBeUndefined();
+ });
+ });
+
+ describe("preferences", () => {
+ it("returns a user state subject", () => {
+ const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
+
+ const subject = provider.preferences({ account$: SomeAccount$ });
+
+ expect(subject).toBeInstanceOf(UserStateSubject);
+ });
+ });
+});
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
new file mode 100644
index 00000000000..f8c07283f5a
--- /dev/null
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
@@ -0,0 +1,252 @@
+import {
+ Observable,
+ combineLatestWith,
+ distinctUntilChanged,
+ map,
+ shareReplay,
+ switchMap,
+ takeUntil,
+} from "rxjs";
+
+import { PolicyType } from "@bitwarden/common/admin-console/enums";
+import { Account } from "@bitwarden/common/auth/abstractions/account.service";
+import { BoundDependency } from "@bitwarden/common/tools/dependencies";
+import { ExtensionSite } from "@bitwarden/common/tools/extension";
+import { SemanticLogger } from "@bitwarden/common/tools/log";
+import { SystemServiceProvider } from "@bitwarden/common/tools/providers";
+import { anyComplete, pin } from "@bitwarden/common/tools/rx";
+import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
+import { UserStateSubjectDependencyProvider } from "@bitwarden/common/tools/state/user-state-subject-dependency-provider";
+
+import {
+ GeneratorMetadata,
+ AlgorithmsByType,
+ CredentialAlgorithm,
+ CredentialType,
+ isForwarderExtensionId,
+ toForwarderMetadata,
+ Type,
+ Algorithms,
+ Types,
+} from "../metadata";
+import { availableAlgorithms_vNext } from "../policies/available-algorithms-policy";
+import { CredentialPreference } from "../types";
+import {
+ AlgorithmRequest,
+ TypeRequest,
+ MetadataRequest,
+ isAlgorithmRequest,
+ isTypeRequest,
+} from "../types/metadata-request";
+
+import { PREFERENCES } from "./credential-preferences";
+
+/** Surfaces contextual information to credential generators */
+export class GeneratorMetadataProvider {
+ /** Instantiates the context provider
+ * @param system dependency providers for user state subjects
+ * @param application dependency providers for system services
+ */
+ constructor(
+ private readonly system: UserStateSubjectDependencyProvider,
+ private readonly application: SystemServiceProvider,
+ algorithms: ReadonlyArray>,
+ ) {
+ this.log = system.log({ type: "GeneratorMetadataProvider" });
+
+ const site = application.extension.site("forwarder");
+ if (!site) {
+ this.log.panic("forwarder extension site not found");
+ }
+ this.site = site;
+
+ this._metadata = new Map(algorithms.map((a) => [a.id, a] as const));
+ }
+
+ private readonly site: ExtensionSite;
+ private readonly log: SemanticLogger;
+
+ private _metadata: Map>;
+
+ /** Retrieve an algorithm's generator metadata
+ * @param algorithm identifies the algorithm
+ * @returns the algorithm's generator metadata
+ * @throws when the algorithm doesn't identify a known metadata entry
+ */
+ metadata(algorithm: CredentialAlgorithm) {
+ let result = null;
+ if (isForwarderExtensionId(algorithm)) {
+ const extension = this.site.extensions.get(algorithm.forwarder);
+ if (!extension) {
+ this.log.panic(algorithm, "extension not found");
+ }
+
+ result = toForwarderMetadata(extension);
+ } else {
+ result = this._metadata.get(algorithm);
+ }
+
+ if (!result) {
+ this.log.panic({ algorithm }, "metadata not found");
+ }
+
+ return result;
+ }
+
+ /** retrieve credential types */
+ types(): ReadonlyArray {
+ return Types;
+ }
+
+ /** Retrieve the credential algorithm ids that match the request.
+ * @param requested when this has a `type` property, the method
+ * returns all algorithms with the same credential type. When this has an `algorithm`
+ * property, the method returns 0 or 1 matching algorithms.
+ * @returns the matching algorithms. This method always returns an array;
+ * the array is empty when no algorithms match the input criteria.
+ * @throws when neither `requested.algorithm` nor `requested.type` contains
+ * a value.
+ * @remarks this method enforces technical requirements only.
+ * If you want these algorithms with policy controls applied, use `algorithms$`.
+ */
+ algorithms(requested: AlgorithmRequest): CredentialAlgorithm[];
+ algorithms(requested: TypeRequest): CredentialAlgorithm[];
+ algorithms(requested: MetadataRequest): CredentialAlgorithm[] {
+ let algorithms: CredentialAlgorithm[];
+ if (isTypeRequest(requested)) {
+ let forwarders: CredentialAlgorithm[] = [];
+ if (requested.type === Type.email) {
+ forwarders = Array.from(this.site.extensions.keys()).map((forwarder) => ({ forwarder }));
+ }
+
+ algorithms = AlgorithmsByType[requested.type].concat(forwarders);
+ } else if (isAlgorithmRequest(requested) && isForwarderExtensionId(requested.algorithm)) {
+ algorithms = this.site.extensions.has(requested.algorithm.forwarder)
+ ? [requested.algorithm]
+ : [];
+ } else if (isAlgorithmRequest(requested)) {
+ algorithms = Algorithms.includes(requested.algorithm) ? [requested.algorithm] : [];
+ } else {
+ this.log.panic(requested, "algorithm or type required");
+ }
+
+ return algorithms;
+ }
+
+ // emits a function that returns `true` when the input algorithm is available
+ private isAvailable$(
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable<(a: CredentialAlgorithm) => boolean> {
+ const id$ = dependencies.account$.pipe(
+ map((account) => account.id),
+ pin(),
+ shareReplay({ bufferSize: 1, refCount: true }),
+ );
+
+ const available$ = id$.pipe(
+ switchMap((id) => {
+ const policies$ = this.application.policy.getAll$(PolicyType.PasswordGenerator, id).pipe(
+ map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
+ map((p) => new Set(p)),
+ // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
+ takeUntil(anyComplete(id$)),
+ );
+ return policies$;
+ }),
+ map(
+ (available) =>
+ function (a: CredentialAlgorithm) {
+ return isForwarderExtensionId(a) || available.has(a);
+ },
+ ),
+ );
+
+ return available$;
+ }
+
+ /** Retrieve credential algorithms filtered by the user's active policy.
+ * @param requested when this has a `type` property, the method
+ * returns all algorithms with a matching credential type. When this has an `algorithm`
+ * property, the method returns 0 or 1 matching algorithms.
+ * @param dependencies.account the account requesting algorithm access;
+ * this parameter controls which policy, if any, is applied.
+ * @returns an observable that emits matching algorithms. When no algorithms
+ * match the request, an empty array is emitted.
+ * @throws when neither `requested.algorithm` nor `requested.type` contains
+ * a value.
+ * @remarks this method applies policy controls. In particular, it excludes
+ * algorithms prohibited by a policy control. If you want lists of algorithms
+ * supported by the client, use `algorithms`.
+ */
+ algorithms$(
+ requested: AlgorithmRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable;
+ algorithms$(
+ requested: TypeRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable;
+ algorithms$(
+ requested: MetadataRequest,
+ dependencies: BoundDependency<"account", Account>,
+ ): Observable {
+ if (isTypeRequest(requested)) {
+ const { type } = requested;
+ return this.isAvailable$(dependencies).pipe(
+ map((isAvailable) => this.algorithms({ type }).filter(isAvailable)),
+ );
+ } else if (isAlgorithmRequest(requested)) {
+ const { algorithm } = requested;
+ return this.isAvailable$(dependencies).pipe(
+ map((isAvailable) => (isAvailable(algorithm) ? [algorithm] : [])),
+ );
+ } else {
+ this.log.panic(requested, "algorithm or type required");
+ }
+ }
+
+ preference$(type: CredentialType, dependencies: BoundDependency<"account", Account>) {
+ const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true }));
+
+ const algorithm$ = this.preferences({ account$ }).pipe(
+ combineLatestWith(this.isAvailable$({ account$ })),
+ map(([preferences, isAvailable]) => {
+ const algorithm: CredentialAlgorithm = preferences[type].algorithm;
+ if (isAvailable(algorithm)) {
+ return algorithm;
+ }
+
+ const algorithms = type ? this.algorithms({ type: type }) : [];
+ // `?? null` because logging types must be `Jsonify`
+ const defaultAlgorithm = algorithms.find(isAvailable) ?? null;
+ this.log.debug(
+ { algorithm, defaultAlgorithm, credentialType: type },
+ "preference not available; defaulting the generator algorithm",
+ );
+
+ // `?? undefined` so that interface is ADR-14 compliant
+ return defaultAlgorithm ?? undefined;
+ }),
+ distinctUntilChanged(),
+ );
+
+ return algorithm$;
+ }
+
+ /** Get a subject bound to credential generator preferences.
+ * @param dependencies.account$ identifies the account to which the preferences are bound
+ * @returns a subject bound to the user's preferences
+ * @remarks Preferences determine which algorithms are used when generating a
+ * credential from a credential type (e.g. `PassX` or `Username`). Preferences
+ * should not be used to hold navigation history. Use @bitwarden/generator-navigation
+ * instead.
+ */
+ preferences(
+ dependencies: BoundDependency<"account", Account>,
+ ): UserStateSubject {
+ // FIXME: enforce policy
+ const subject = new UserStateSubject(PREFERENCES, this.system, dependencies);
+
+ return subject;
+ }
+}
diff --git a/libs/tools/generator/core/src/types/credential-generator-configuration.ts b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
index 08aec48a9e7..36b0f3046a9 100644
--- a/libs/tools/generator/core/src/types/credential-generator-configuration.ts
+++ b/libs/tools/generator/core/src/types/credential-generator-configuration.ts
@@ -133,7 +133,9 @@ export type CredentialGeneratorConfiguration = CredentialGener
};
/** Defines the stored parameters for credential generation */
settings: {
- /** value used when an account's settings haven't been initialized */
+ /** value used when an account's settings haven't been initialized
+ * @deprecated use `ObjectKey.initial` for your desired storage property instead
+ */
initial: Readonly>;
/** Application-global constraints that apply to account settings */
diff --git a/libs/tools/generator/core/src/types/generator-type.ts b/libs/tools/generator/core/src/types/generator-type.ts
index 5b74d17fa4a..c75e4329610 100644
--- a/libs/tools/generator/core/src/types/generator-type.ts
+++ b/libs/tools/generator/core/src/types/generator-type.ts
@@ -1,6 +1,8 @@
+import { VendorId } from "@bitwarden/common/tools/extension";
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types";
+import { AlgorithmsByType, CredentialType } from "../metadata";
/** A type of password that may be generated by the credential generator. */
export type PasswordAlgorithm = (typeof PasswordAlgorithms)[number];
@@ -11,7 +13,7 @@ export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number];
/** A type of email address that may be generated by the credential generator. */
export type EmailAlgorithm = (typeof EmailAlgorithms)[number];
-export type ForwarderIntegration = { forwarder: IntegrationId };
+export type ForwarderIntegration = { forwarder: IntegrationId & VendorId };
/** Returns true when the input algorithm is a forwarder integration. */
export function isForwarderIntegration(
@@ -74,8 +76,8 @@ export type CredentialCategory = keyof typeof CredentialCategories;
/** The kind of credential to generate using a compound configuration. */
// FIXME: extend the preferences to include a preferred forwarder
export type CredentialPreference = {
- [Key in CredentialCategory]: {
- algorithm: (typeof CredentialCategories)[Key][number];
+ [Key in CredentialType & CredentialCategory]: {
+ algorithm: CredentialAlgorithm & (typeof AlgorithmsByType)[Key][number];
updated: Date;
};
};
diff --git a/libs/tools/generator/core/src/types/metadata-request.ts b/libs/tools/generator/core/src/types/metadata-request.ts
new file mode 100644
index 00000000000..e9cae7060f0
--- /dev/null
+++ b/libs/tools/generator/core/src/types/metadata-request.ts
@@ -0,0 +1,13 @@
+import { CredentialAlgorithm, CredentialType } from "../metadata";
+
+export type AlgorithmRequest = { algorithm: CredentialAlgorithm };
+export type TypeRequest = { type: CredentialType };
+export type MetadataRequest = Partial;
+
+export function isAlgorithmRequest(request: MetadataRequest): request is AlgorithmRequest {
+ return !!request.algorithm;
+}
+
+export function isTypeRequest(request: MetadataRequest): request is TypeRequest {
+ return !!request.type;
+}
From f3a26497520e4fccc3365965811dcb72256156db Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Tue, 25 Mar 2025 16:34:43 -0500
Subject: [PATCH 12/66] refactor(auth): [PM-18148] replace app-link-sso
directive with LinkSsoService
Removes the app-link-sso directive and adds a LinkSsoService which is used to link an organization with SSO.
Resolves PM-18148
---
apps/web/src/app/auth/core/services/index.ts | 1 +
.../core/services/link-sso.service.spec.ts | 154 ++++++++++++++++++
.../auth/core/services/link-sso.service.ts | 91 +++++++++++
apps/web/src/app/core/core.module.ts | 13 ++
.../components/link-sso.directive.ts | 26 ---
.../organization-options.component.html | 4 +-
.../organization-options.component.ts | 33 +++-
.../vault-filter/vault-filter.module.ts | 3 +-
8 files changed, 287 insertions(+), 38 deletions(-)
create mode 100644 apps/web/src/app/auth/core/services/link-sso.service.spec.ts
create mode 100644 apps/web/src/app/auth/core/services/link-sso.service.ts
delete mode 100644 apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts
index 1e8eec759b1..11c8dd98872 100644
--- a/apps/web/src/app/auth/core/services/index.ts
+++ b/apps/web/src/app/auth/core/services/index.ts
@@ -4,3 +4,4 @@ export * from "./webauthn-login";
export * from "./set-password-jit";
export * from "./registration";
export * from "./two-factor-auth";
+export * from "./link-sso.service";
diff --git a/apps/web/src/app/auth/core/services/link-sso.service.spec.ts b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts
new file mode 100644
index 00000000000..70b52999875
--- /dev/null
+++ b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts
@@ -0,0 +1,154 @@
+import { mock, MockProxy } from "jest-mock-extended";
+import { BehaviorSubject } from "rxjs";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
+import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
+import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+import {
+ PasswordGenerationServiceAbstraction,
+ PasswordGeneratorOptions,
+} from "@bitwarden/generator-legacy";
+
+import { LinkSsoService } from "./link-sso.service";
+
+describe("LinkSsoService", () => {
+ let sut: LinkSsoService;
+
+ let mockSsoLoginService: MockProxy;
+ let mockApiService: MockProxy;
+ let mockCryptoFunctionService: MockProxy;
+ let mockEnvironmentService: MockProxy;
+ let mockPasswordGenerationService: MockProxy;
+ let mockPlatformUtilsService: MockProxy;
+
+ const mockEnvironment$ = new BehaviorSubject({
+ getIdentityUrl: jest.fn().mockReturnValue("https://identity.bitwarden.com"),
+ });
+
+ beforeEach(() => {
+ // Create mock implementations
+ mockSsoLoginService = mock();
+ mockApiService = mock();
+ mockCryptoFunctionService = mock();
+ mockEnvironmentService = mock();
+ mockPasswordGenerationService = mock();
+ mockPlatformUtilsService = mock();
+
+ // Set up environment service to return our mock environment
+ mockEnvironmentService.environment$ = mockEnvironment$;
+
+ // Set up API service mocks
+ const mockResponse = { Token: "mockSsoToken" };
+ mockApiService.preValidateSso.mockResolvedValue(new SsoPreValidateResponse(mockResponse));
+ mockApiService.getSsoUserIdentifier.mockResolvedValue("mockUserIdentifier");
+
+ // Set up password generation service mock
+ mockPasswordGenerationService.generatePassword.mockImplementation(
+ async (options: PasswordGeneratorOptions) => {
+ return "mockGeneratedPassword";
+ },
+ );
+
+ // Set up crypto function service mock
+ mockCryptoFunctionService.hash.mockResolvedValue(new Uint8Array([1, 2, 3, 4]));
+
+ // Create the service under test with mock dependencies
+ sut = new LinkSsoService(
+ mockSsoLoginService,
+ mockApiService,
+ mockCryptoFunctionService,
+ mockEnvironmentService,
+ mockPasswordGenerationService,
+ mockPlatformUtilsService,
+ );
+
+ // Mock Utils.fromBufferToUrlB64
+ jest.spyOn(Utils, "fromBufferToUrlB64").mockReturnValue("mockCodeChallenge");
+
+ // Mock window.location
+ Object.defineProperty(window, "location", {
+ value: {
+ origin: "https://bitwarden.com",
+ },
+ writable: true,
+ });
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe("linkSso", () => {
+ it("throws an error when identifier is null", async () => {
+ await expect(sut.linkSso(null as unknown as string)).rejects.toThrow(
+ "SSO identifier is required",
+ );
+ });
+
+ it("throws an error when identifier is empty", async () => {
+ await expect(sut.linkSso("")).rejects.toThrow("SSO identifier is required");
+ });
+
+ it("calls preValidateSso with the provided identifier", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockApiService.preValidateSso).toHaveBeenCalledWith("org123");
+ });
+
+ it("generates a password for code verifier", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockPasswordGenerationService.generatePassword).toHaveBeenCalledWith({
+ type: "password",
+ length: 64,
+ uppercase: true,
+ lowercase: true,
+ number: true,
+ special: false,
+ });
+ });
+
+ it("sets the code verifier in the ssoLoginService", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockSsoLoginService.setCodeVerifier).toHaveBeenCalledWith("mockGeneratedPassword");
+ });
+
+ it("generates a state and sets it in the ssoLoginService", async () => {
+ await sut.linkSso("org123");
+
+ const expectedState =
+ "mockGeneratedPassword_returnUri='/settings/organizations'_identifier=org123";
+ expect(mockSsoLoginService.setSsoState).toHaveBeenCalledWith(expectedState);
+ });
+
+ it("gets the SSO user identifier from the API", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockApiService.getSsoUserIdentifier).toHaveBeenCalled();
+ });
+
+ it("launches the authorize URL with the correct parameters", async () => {
+ await sut.linkSso("org123");
+
+ expect(mockPlatformUtilsService.launchUri).toHaveBeenCalledWith(
+ expect.stringContaining("https://identity.bitwarden.com/connect/authorize"),
+ { sameWindow: true },
+ );
+
+ const launchUriArg = mockPlatformUtilsService.launchUri.mock.calls[0][0];
+ expect(launchUriArg).toContain("client_id=web");
+ expect(launchUriArg).toContain(
+ "redirect_uri=https%3A%2F%2Fbitwarden.com%2Fsso-connector.html",
+ );
+ expect(launchUriArg).toContain("response_type=code");
+ expect(launchUriArg).toContain("code_challenge=mockCodeChallenge");
+ expect(launchUriArg).toContain("ssoToken=mockSsoToken");
+ expect(launchUriArg).toContain("user_identifier=mockUserIdentifier");
+ });
+ });
+});
diff --git a/apps/web/src/app/auth/core/services/link-sso.service.ts b/apps/web/src/app/auth/core/services/link-sso.service.ts
new file mode 100644
index 00000000000..3d51525add1
--- /dev/null
+++ b/apps/web/src/app/auth/core/services/link-sso.service.ts
@@ -0,0 +1,91 @@
+import { firstValueFrom } from "rxjs";
+
+import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
+import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
+import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+import { Utils } from "@bitwarden/common/platform/misc/utils";
+import {
+ PasswordGenerationServiceAbstraction,
+ PasswordGeneratorOptions,
+} from "@bitwarden/generator-legacy";
+
+/**
+ * Provides a service for linking SSO.
+ */
+export class LinkSsoService {
+ constructor(
+ private ssoLoginService: SsoLoginServiceAbstraction,
+ private apiService: ApiService,
+ private cryptoFunctionService: CryptoFunctionService,
+ private environmentService: EnvironmentService,
+ private passwordGenerationService: PasswordGenerationServiceAbstraction,
+ private platformUtilsService: PlatformUtilsService,
+ ) {}
+
+ /**
+ * Links SSO to an organization.
+ * Ported from the SsoComponent
+ * @param identifier The identifier of the organization to link to.
+ */
+ async linkSso(identifier: string) {
+ if (identifier == null || identifier === "") {
+ throw new Error("SSO identifier is required");
+ }
+
+ const redirectUri = window.location.origin + "/sso-connector.html";
+ const clientId = "web";
+ const returnUri = "/settings/organizations";
+
+ const response = await this.apiService.preValidateSso(identifier);
+
+ const passwordOptions: PasswordGeneratorOptions = {
+ type: "password",
+ length: 64,
+ uppercase: true,
+ lowercase: true,
+ number: true,
+ special: false,
+ };
+
+ const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
+ const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
+ const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
+ await this.ssoLoginService.setCodeVerifier(codeVerifier);
+
+ let state = await this.passwordGenerationService.generatePassword(passwordOptions);
+ state += `_returnUri='${returnUri}'`;
+ state += `_identifier=${identifier}`;
+
+ // Save state
+ await this.ssoLoginService.setSsoState(state);
+
+ const env = await firstValueFrom(this.environmentService.environment$);
+
+ let authorizeUrl =
+ env.getIdentityUrl() +
+ "/connect/authorize?" +
+ "client_id=" +
+ clientId +
+ "&redirect_uri=" +
+ encodeURIComponent(redirectUri) +
+ "&" +
+ "response_type=code&scope=api offline_access&" +
+ "state=" +
+ state +
+ "&code_challenge=" +
+ codeChallenge +
+ "&" +
+ "code_challenge_method=S256&response_mode=query&" +
+ "domain_hint=" +
+ encodeURIComponent(identifier) +
+ "&ssoToken=" +
+ encodeURIComponent(response.token);
+
+ const userIdentifier = await this.apiService.getSsoUserIdentifier();
+ authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
+
+ this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
+ }
+}
diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts
index cc9024490d6..9e6f88d18d6 100644
--- a/apps/web/src/app/core/core.module.ts
+++ b/apps/web/src/app/core/core.module.ts
@@ -116,6 +116,7 @@ import {
WebLoginDecryptionOptionsService,
WebTwoFactorAuthComponentService,
WebTwoFactorAuthDuoComponentService,
+ LinkSsoService,
} from "../auth";
import { WebSsoComponentService } from "../auth/core/services/login/web-sso-component.service";
import { AcceptOrganizationInviteService } from "../auth/organization-invite/accept-organization.service";
@@ -345,6 +346,18 @@ const safeProviders: SafeProvider[] = [
useClass: WebSsoComponentService,
deps: [I18nServiceAbstraction],
}),
+ safeProvider({
+ provide: LinkSsoService,
+ useClass: LinkSsoService,
+ deps: [
+ SsoLoginServiceAbstraction,
+ ApiService,
+ CryptoFunctionService,
+ EnvironmentService,
+ PasswordGenerationServiceAbstraction,
+ PlatformUtilsService,
+ ],
+ }),
safeProvider({
provide: TwoFactorAuthDuoComponentService,
useClass: WebTwoFactorAuthDuoComponentService,
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
deleted file mode 100644
index a1781889c49..00000000000
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/link-sso.directive.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { AfterContentInit, Directive, HostListener, Input } from "@angular/core";
-
-import { SsoComponent } from "@bitwarden/angular/auth/components/sso.component";
-import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-
-@Directive({
- selector: "[app-link-sso]",
-})
-export class LinkSsoDirective extends SsoComponent implements AfterContentInit {
- @Input() organization: Organization;
- returnUri = "/settings/organizations";
- redirectUri = window.location.origin + "/sso-connector.html";
- clientId = "web";
-
- @HostListener("click", ["$event"])
- async onClick($event: MouseEvent) {
- $event.preventDefault();
- await this.submit(this.returnUri, true);
- }
-
- async ngAfterContentInit() {
- this.identifier = this.organization.identifier;
- }
-}
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
index 0b94b6e2be2..0fe243ed20a 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.html
@@ -50,10 +50,10 @@
{{ "unlinkSso" | i18n }}
-
+
{{ "linkSso" | i18n }}
-
+
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 4b9791c61bf..4abf8a9ee9c 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
@@ -1,5 +1,3 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import {
combineLatest,
@@ -37,6 +35,7 @@ import { DialogService, ToastService } from "@bitwarden/components";
import { OrganizationUserResetPasswordService } from "../../../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
import { EnrollMasterPasswordReset } from "../../../../admin-console/organizations/users/enroll-master-password-reset.component";
+import { LinkSsoService } from "../../../../auth/core/services";
import { OptionsInput } from "../shared/components/vault-filter-section.component";
import { OrganizationFilter } from "../shared/models/vault-filter.type";
@@ -45,12 +44,12 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type";
templateUrl: "organization-options.component.html",
})
export class OrganizationOptionsComponent implements OnInit, OnDestroy {
- protected actionPromise: Promise;
+ protected actionPromise?: Promise;
protected resetPasswordPolicy?: Policy | undefined;
protected loaded = false;
protected hideMenu = false;
protected showLeaveOrgOption = false;
- protected organization: OrganizationFilter;
+ protected organization!: OrganizationFilter;
private destroy$ = new Subject();
@@ -72,6 +71,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
private configService: ConfigService,
private organizationService: OrganizationService,
private accountService: AccountService,
+ private linkSsoService: LinkSsoService,
) {}
async ngOnInit() {
@@ -147,6 +147,23 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
return org?.useSso && org?.identifier;
}
+ /**
+ * Links SSO to an organization.
+ * @param organization The organization to link SSO to.
+ */
+ async handleLinkSso(organization: Organization) {
+ try {
+ await this.linkSsoService.linkSso(organization.identifier);
+ } catch (e) {
+ this.logService.error(e);
+ this.toastService.showToast({
+ variant: "error",
+ title: "",
+ message: this.i18nService.t("errorOccurred"),
+ });
+ }
+ }
+
async unlinkSso(org: Organization) {
const confirmed = await this.dialogService.openSimpleDialog({
title: org.name,
@@ -165,7 +182,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("unlinkedSso"),
});
} catch (e) {
@@ -189,7 +206,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("leftOrganization"),
});
} catch (e) {
@@ -215,7 +232,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
// Remove reset password
const request = new OrganizationUserResetPasswordEnrollmentRequest();
request.masterPasswordHash = "ignored";
- request.resetPasswordKey = null;
+ request.resetPasswordKey = "";
this.actionPromise =
this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment(
this.organization.id,
@@ -226,7 +243,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy {
await this.actionPromise;
this.toastService.showToast({
variant: "success",
- title: null,
+ title: "",
message: this.i18nService.t("withdrawPasswordResetSuccess"),
});
await this.syncService.fullSync(true);
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts b/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
index 641e4352696..1f2d5652735 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/vault-filter.module.ts
@@ -4,7 +4,6 @@ import { SearchModule } from "@bitwarden/components";
import { VaultFilterSharedModule } from "../../individual-vault/vault-filter/shared/vault-filter-shared.module";
-import { LinkSsoDirective } from "./components/link-sso.directive";
import { OrganizationOptionsComponent } from "./components/organization-options.component";
import { VaultFilterComponent } from "./components/vault-filter.component";
import { VaultFilterService as VaultFilterServiceAbstraction } from "./services/abstractions/vault-filter.service";
@@ -12,7 +11,7 @@ import { VaultFilterService } from "./services/vault-filter.service";
@NgModule({
imports: [VaultFilterSharedModule, SearchModule],
- declarations: [VaultFilterComponent, OrganizationOptionsComponent, LinkSsoDirective],
+ declarations: [VaultFilterComponent, OrganizationOptionsComponent],
exports: [VaultFilterComponent],
providers: [
{
From 2b9ef7fe195152adc0e7efae2e0623fa1e755f6c Mon Sep 17 00:00:00 2001
From: Addison Beck
Date: Tue, 25 Mar 2025 19:11:26 -0400
Subject: [PATCH 13/66] fix: use newly-minted policy methods in metadata
provider (#13993)
Recently the policy service was refactored and some method signatures were
changed. One of these changes involved renaming the `getAll` observable to
`policiesByType`.
This was not merged into the metadata provider work before it was merged, so
those changes were committed using removed method signatures.
This commit updates these references.
---
.../services/generator-metadata-provider.spec.ts | 14 +++++++-------
.../src/services/generator-metadata-provider.ts | 14 ++++++++------
2 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
index 958e5608449..37a987f88bc 100644
--- a/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.spec.ts
@@ -295,7 +295,7 @@ describe("GeneratorMetadataProvider", () => {
[Algorithm.username, effWordList],
[Algorithm.password, password],
])("gets a specific algorithm", async (algorithm, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
metadata,
]);
@@ -311,7 +311,7 @@ describe("GeneratorMetadataProvider", () => {
[Type.username, [effWordList]],
[Type.password, [password, passphrase]],
])("gets a category of algorithms", async (category, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
const result = new ReplaySubject(1);
@@ -329,7 +329,7 @@ describe("GeneratorMetadataProvider", () => {
overridePasswordType: Algorithm.password,
},
} as any);
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([policy]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([policy]));
const metadata = [password, passphrase];
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, metadata);
const algorithmResult = new ReplaySubject(1);
@@ -347,7 +347,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("omits algorithms whose metadata is unavailable", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
password,
]);
@@ -389,7 +389,7 @@ describe("GeneratorMetadataProvider", () => {
[Type.username, effWordList],
[Type.password, password],
])("emits the user's %s preference", async (type, metadata) => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
metadata,
]);
@@ -401,7 +401,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("emits a default when the user's preference is unavailable", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, [
plusAddress,
]);
@@ -416,7 +416,7 @@ describe("GeneratorMetadataProvider", () => {
});
it("emits undefined when the user's preference is unavailable and there is no metadata", async () => {
- SomePolicyService.getAll$.mockReturnValue(new BehaviorSubject([]));
+ SomePolicyService.policiesByType$.mockReturnValue(new BehaviorSubject([]));
const provider = new GeneratorMetadataProvider(SystemProvider, ApplicationProvider, []);
const result = new ReplaySubject(1);
diff --git a/libs/tools/generator/core/src/services/generator-metadata-provider.ts b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
index f8c07283f5a..161f7192c39 100644
--- a/libs/tools/generator/core/src/services/generator-metadata-provider.ts
+++ b/libs/tools/generator/core/src/services/generator-metadata-provider.ts
@@ -145,12 +145,14 @@ export class GeneratorMetadataProvider {
const available$ = id$.pipe(
switchMap((id) => {
- const policies$ = this.application.policy.getAll$(PolicyType.PasswordGenerator, id).pipe(
- map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
- map((p) => new Set(p)),
- // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
- takeUntil(anyComplete(id$)),
- );
+ const policies$ = this.application.policy
+ .policiesByType$(PolicyType.PasswordGenerator, id)
+ .pipe(
+ map((p) => availableAlgorithms_vNext(p).filter((a) => this._metadata.has(a))),
+ map((p) => new Set(p)),
+ // complete policy emissions otherwise `switchMap` holds `available$` open indefinitely
+ takeUntil(anyComplete(id$)),
+ );
return policies$;
}),
map(
From 279b1a90dddad8637bfd11923a44e9f2050fccda Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Wed, 26 Mar 2025 09:44:52 +0100
Subject: [PATCH 14/66] resolve the hover effect issue (#13981)
---
.../admin-console/organizations/manage/events.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.html b/apps/web/src/app/admin-console/organizations/manage/events.component.html
index 4a48ae1ead7..adadec5075a 100644
--- a/apps/web/src/app/admin-console/organizations/manage/events.component.html
+++ b/apps/web/src/app/admin-console/organizations/manage/events.component.html
@@ -111,7 +111,7 @@
Date: Wed, 26 Mar 2025 12:49:53 +0000
Subject: [PATCH 15/66] [PM-19046] Update README mobile references (#13990)
* Update mobile references in README.md
* Matching ios / android repo titles
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 22c8d329f1c..cdeaa4c8cf7 100644
--- a/README.md
+++ b/README.md
@@ -13,15 +13,15 @@
# Bitwarden Client Applications
-This repository houses all Bitwarden client applications except the [Mobile application](https://github.com/bitwarden/mobile).
+This repository houses all Bitwarden client applications except the mobile applications ([iOS](https://github.com/bitwarden/ios) | [android](https://github.com/bitwarden/android)).
Please refer to the [Clients section](https://contributing.bitwarden.com/getting-started/clients/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
## Related projects:
- [bitwarden/server](https://github.com/bitwarden/server): The core infrastructure backend (API, database, Docker, etc).
-- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden mobile app for iOS.
-- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden mobile app for Android.
+- [bitwarden/ios](https://github.com/bitwarden/ios): Bitwarden iOS Password Manager & Authenticator apps.
+- [bitwarden/android](https://github.com/bitwarden/android): Bitwarden Android Password Manager & Authenticator apps.
- [bitwarden/directory-connector](https://github.com/bitwarden/directory-connector): A tool for syncing a directory (AD, LDAP, Azure, G Suite, Okta) to an organization.
# We're Hiring!
From be8c5f28b5807c9682abd652294a4b3746cf5892 Mon Sep 17 00:00:00 2001
From: Jonas Hendrickx
Date: Wed, 26 Mar 2025 17:59:27 +0100
Subject: [PATCH 16/66] [PM-18170] Remove
'PM-15814-alert-owners-of-reseller-managed-orgs' feature flag (#13757)
---
.../organizations/collections/vault.component.ts | 8 +-------
.../src/app/billing/services/trial-flow.service.ts | 13 ++-----------
libs/common/src/enums/feature-flag.enum.ts | 2 --
3 files changed, 3 insertions(+), 20 deletions(-)
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
index ec92597dc7b..8dfebea5229 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
+++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts
@@ -45,7 +45,6 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions";
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";
@@ -196,7 +195,6 @@ export class VaultComponent implements OnInit, OnDestroy {
private refresh$ = new BehaviorSubject(null);
private destroy$ = new Subject();
protected addAccessStatus$ = new BehaviorSubject(0);
- private resellerManagedOrgAlert: boolean;
private vaultItemDialogRef?: DialogRef | undefined;
private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe(
@@ -264,10 +262,6 @@ export class VaultComponent implements OnInit, OnDestroy {
async ngOnInit() {
this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
- this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
- FeatureFlag.ResellerManagedOrgAlert,
- );
-
this.trashCleanupWarning = this.i18nService.t(
this.platformUtilsService.isSelfHost()
? "trashCleanupWarningSelfHosted"
@@ -654,7 +648,7 @@ export class VaultComponent implements OnInit, OnDestroy {
);
this.resellerWarning$ = organization$.pipe(
- filter((org) => org.isOwner && this.resellerManagedOrgAlert),
+ filter((org) => org.isOwner),
switchMap((org) =>
from(this.billingApiService.getOrganizationBillingMetadata(org.id)).pipe(
map((metadata) => ({ org, metadata })),
diff --git a/apps/web/src/app/billing/services/trial-flow.service.ts b/apps/web/src/app/billing/services/trial-flow.service.ts
index eb08e5bd7ad..979fc29aed7 100644
--- a/apps/web/src/app/billing/services/trial-flow.service.ts
+++ b/apps/web/src/app/billing/services/trial-flow.service.ts
@@ -11,8 +11,6 @@ import { BillingSourceResponse } from "@bitwarden/common/billing/models/response
import { OrganizationBillingMetadataResponse } from "@bitwarden/common/billing/models/response/organization-billing-metadata.response";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PaymentSourceResponse } from "@bitwarden/common/billing/models/response/payment-source.response";
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { DialogService } from "@bitwarden/components";
@@ -24,15 +22,12 @@ import { FreeTrial } from "../types/free-trial";
@Injectable({ providedIn: "root" })
export class TrialFlowService {
- private resellerManagedOrgAlert: boolean;
-
constructor(
private i18nService: I18nService,
protected dialogService: DialogService,
private router: Router,
protected billingApiService: BillingApiServiceAbstraction,
private organizationApiService: OrganizationApiServiceAbstraction,
- private configService: ConfigService,
) {}
checkForOrgsWithUpcomingPaymentIssues(
organization: Organization,
@@ -98,10 +93,6 @@ export class TrialFlowService {
isCanceled: boolean,
isUnpaid: boolean,
): Promise {
- this.resellerManagedOrgAlert = await this.configService.getFeatureFlag(
- FeatureFlag.ResellerManagedOrgAlert,
- );
-
if (!org?.isOwner && !org.providerId) {
await this.dialogService.openSimpleDialog({
title: this.i18nService.t("suspendedOrganizationTitle", org?.name),
@@ -113,7 +104,7 @@ export class TrialFlowService {
return false;
}
- if (org.providerId && this.resellerManagedOrgAlert) {
+ if (org.providerId) {
await this.dialogService.openSimpleDialog({
title: this.i18nService.t("suspendedOrganizationTitle", org.name),
content: { key: "suspendedManagedOrgMessage", placeholders: [org.providerName] },
@@ -134,7 +125,7 @@ export class TrialFlowService {
});
}
- if (org.isOwner && isCanceled && this.resellerManagedOrgAlert) {
+ if (org.isOwner && isCanceled) {
await this.changePlan(org);
}
}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 5e52eb31840..e3c15a1d40b 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -46,7 +46,6 @@ export enum FeatureFlag {
TrialPaymentOptional = "PM-8163-trial-payment",
MacOsNativeCredentialSync = "macos-native-credential-sync",
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
- ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs",
AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
@@ -107,7 +106,6 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.TrialPaymentOptional]: FALSE,
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
- [FeatureFlag.ResellerManagedOrgAlert]: FALSE,
[FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
From a3e01ad672575532c8eac4187562d66663db2ad1 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Wed, 26 Mar 2025 13:16:40 -0400
Subject: [PATCH 17/66] [PM-10610] push notification to end user notification
service (#13876)
* use NotificationsService.notifictions$ for tracking inside default end user notification
---
.../src/services/jslib-services.module.ts | 2 +-
.../src/enums/notification-type.enum.ts | 2 ++
.../internal/noop-notifications.service.ts | 7 ++++-
.../notifications/notifications.service.ts | 6 +++-
.../end-user-notification.service.ts | 7 -----
...ault-end-user-notification.service.spec.ts | 9 +++++-
.../default-end-user-notification.service.ts | 28 +++++++++++++++----
7 files changed, 45 insertions(+), 16 deletions(-)
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 3a28f28caaf..37220b5195d 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1480,7 +1480,7 @@ const safeProviders: SafeProvider[] = [
safeProvider({
provide: EndUserNotificationService,
useClass: DefaultEndUserNotificationService,
- deps: [StateProvider, ApiServiceAbstraction],
+ deps: [StateProvider, ApiServiceAbstraction, NotificationsService],
}),
safeProvider({
provide: DeviceTrustToastServiceAbstraction,
diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts
index db59fcafa69..c366af1eb61 100644
--- a/libs/common/src/enums/notification-type.enum.ts
+++ b/libs/common/src/enums/notification-type.enum.ts
@@ -24,4 +24,6 @@ export enum NotificationType {
SyncOrganizations = 17,
SyncOrganizationStatusChanged = 18,
SyncOrganizationCollectionSettingChanged = 19,
+ Notification = 20,
+ NotificationStatus = 21,
}
diff --git a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
index f79cabfca8a..9c2435fb1a7 100644
--- a/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
+++ b/libs/common/src/platform/notifications/internal/noop-notifications.service.ts
@@ -1,9 +1,14 @@
-import { Subscription } from "rxjs";
+import { Observable, Subject, Subscription } from "rxjs";
+
+import { NotificationResponse } from "@bitwarden/common/models/response/notification.response";
+import { UserId } from "@bitwarden/common/types/guid";
import { LogService } from "../../abstractions/log.service";
import { NotificationsService } from "../notifications.service";
export class NoopNotificationsService implements NotificationsService {
+ notifications$: Observable = new Subject();
+
constructor(private logService: LogService) {}
startListening(): Subscription {
diff --git a/libs/common/src/platform/notifications/notifications.service.ts b/libs/common/src/platform/notifications/notifications.service.ts
index aa4ff2a57a6..2adc66e361f 100644
--- a/libs/common/src/platform/notifications/notifications.service.ts
+++ b/libs/common/src/platform/notifications/notifications.service.ts
@@ -1,9 +1,13 @@
-import { Subscription } from "rxjs";
+import { Observable, Subscription } from "rxjs";
+
+import { NotificationResponse } from "@bitwarden/common/models/response/notification.response";
+import { UserId } from "@bitwarden/common/types/guid";
/**
* A service offering abilities to interact with push notifications from the server.
*/
export abstract class NotificationsService {
+ abstract notifications$: Observable;
/**
* Starts automatic listening and processing of notifications, should only be called once per application,
* or you will risk notifications being processed multiple times.
diff --git a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
index 2ed7e1de631..fe2852994f7 100644
--- a/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
+++ b/libs/vault/src/notifications/abstractions/end-user-notification.service.ts
@@ -34,13 +34,6 @@ export abstract class EndUserNotificationService {
*/
abstract markAsDeleted(notificationId: any, userId: UserId): Promise;
- /**
- * Create/update a notification in the state for the user specified within the notification.
- * @remarks This method should only be called when a notification payload is received from the web socket.
- * @param notification
- */
- abstract upsert(notification: Notification): Promise;
-
/**
* Clear all notifications from state for the given user.
* @param userId
diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
index ac4304998bc..1d7b2e5aa19 100644
--- a/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
+++ b/libs/vault/src/notifications/services/default-end-user-notification.service.spec.ts
@@ -1,7 +1,8 @@
import { TestBed } from "@angular/core/testing";
-import { firstValueFrom } from "rxjs";
+import { firstValueFrom, of } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { NotificationsService } from "@bitwarden/common/platform/notifications";
import { StateProvider } from "@bitwarden/common/platform/state";
import { NotificationId, UserId } from "@bitwarden/common/types/guid";
import { DefaultEndUserNotificationService } from "@bitwarden/vault";
@@ -36,6 +37,12 @@ describe("End User Notification Center Service", () => {
send: mockApiSend,
},
},
+ {
+ provide: NotificationsService,
+ useValue: {
+ notifications$: of(null),
+ },
+ },
],
});
});
diff --git a/libs/vault/src/notifications/services/default-end-user-notification.service.ts b/libs/vault/src/notifications/services/default-end-user-notification.service.ts
index 517a968f8af..404cb7c75c7 100644
--- a/libs/vault/src/notifications/services/default-end-user-notification.service.ts
+++ b/libs/vault/src/notifications/services/default-end-user-notification.service.ts
@@ -1,8 +1,10 @@
import { Injectable } from "@angular/core";
-import { map, Observable, switchMap } from "rxjs";
+import { concatMap, filter, map, Observable, switchMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { NotificationType } from "@bitwarden/common/enums";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
+import { NotificationsService } from "@bitwarden/common/platform/notifications";
import { StateProvider } from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
@@ -14,12 +16,30 @@ import { NOTIFICATIONS } from "../state/end-user-notification.state";
/**
* A service for retrieving and managing notifications for end users.
*/
-@Injectable()
+@Injectable({
+ providedIn: "root",
+})
export class DefaultEndUserNotificationService implements EndUserNotificationService {
constructor(
private stateProvider: StateProvider,
private apiService: ApiService,
- ) {}
+ private defaultNotifications: NotificationsService,
+ ) {
+ this.defaultNotifications.notifications$
+ .pipe(
+ filter(
+ ([notification]) =>
+ notification.type === NotificationType.Notification ||
+ notification.type === NotificationType.NotificationStatus,
+ ),
+ concatMap(([notification, userId]) =>
+ this.updateNotificationState(userId, [
+ new NotificationViewData(notification.payload as NotificationViewResponse),
+ ]),
+ ),
+ )
+ .subscribe();
+ }
notifications$ = perUserCache$((userId: UserId): Observable => {
return this.notificationState(userId).state$.pipe(
@@ -58,8 +78,6 @@ export class DefaultEndUserNotificationService implements EndUserNotificationSer
await this.getNotifications(userId);
}
- upsert(notification: Notification): any {}
-
async clearState(userId: UserId): Promise {
await this.updateNotificationState(userId, []);
}
From 6d2231d1b2eb4d2f046eb94f0e288ba2da897e0f Mon Sep 17 00:00:00 2001
From: Github Actions
Date: Wed, 26 Mar 2025 21:20:59 +0000
Subject: [PATCH 18/66] 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 5a8ddd03b41..6ef35d88c10 100644
--- a/apps/browser/package.json
+++ b/apps/browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@bitwarden/browser",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"scripts": {
"build": "npm run build:chrome",
"build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack",
diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json
index 4510c2f342d..af2bf72b7b5 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": "__MSG_appName__",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"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 fc897c1b1c3..131724e38f9 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": "__MSG_appName__",
- "version": "2025.3.1",
+ "version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
"author": "Bitwarden Inc.",
diff --git a/package-lock.json b/package-lock.json
index 7331fcdde44..19b1772e4bc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -189,7 +189,7 @@
},
"apps/browser": {
"name": "@bitwarden/browser",
- "version": "2025.3.1"
+ "version": "2025.3.2"
},
"apps/cli": {
"name": "@bitwarden/cli",
From 4734cab9a6374e0b2647eadfa01aa9e83f5a1a7e Mon Sep 17 00:00:00 2001
From: Vicki League
Date: Wed, 26 Mar 2025 17:26:05 -0400
Subject: [PATCH 19/66] [CL-550] Fix popup layout padding and adjust views with
extra bottom space (#13562)
* [CL-550] Fix popup layout padding and adjust views with extra bottom space
* simplify css
* fix add/edit item page
* cr changes
* fix failing test
---------
Co-authored-by: Will Martin
---
.../popup/settings/account-security.component.html | 2 +-
.../autofill/popup/settings/autofill.component.html | 4 ++--
.../popup/settings/notifications.component.html | 2 +-
.../platform/popup/layout/popup-page.component.html | 2 +-
.../components/options/send-options.component.html | 2 +-
.../send-details/send-details.component.html | 2 +-
.../send-list-items-container.component.html | 2 +-
.../additional-options-section.component.html | 10 ++++++++--
.../additional-options-section.component.spec.ts | 6 ++++--
.../additional-options-section.component.ts | 4 +++-
.../cipher-form/components/cipher-form.component.html | 4 +++-
.../custom-fields/custom-fields.component.html | 2 +-
.../custom-fields/custom-fields.component.ts | 3 +++
.../item-history/item-history-v2.component.html | 2 +-
14 files changed, 31 insertions(+), 16 deletions(-)
diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html
index 3c5fd7a6af8..b8252aa6e13 100644
--- a/apps/browser/src/auth/popup/settings/account-security.component.html
+++ b/apps/browser/src/auth/popup/settings/account-security.component.html
@@ -95,7 +95,7 @@
-
+
{{ "otherOptions" | i18n }}
diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.html b/apps/browser/src/autofill/popup/settings/autofill.component.html
index 340197f6bf3..c690eb3d2ca 100644
--- a/apps/browser/src/autofill/popup/settings/autofill.component.html
+++ b/apps/browser/src/autofill/popup/settings/autofill.component.html
@@ -209,7 +209,7 @@
-
+
{{ "additionalOptions" | i18n }}
@@ -270,7 +270,7 @@
-
+
{{ "blockedDomains" | i18n }}
diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html
index c6446012d0c..385db8c059b 100644
--- a/apps/browser/src/autofill/popup/settings/notifications.component.html
+++ b/apps/browser/src/autofill/popup/settings/notifications.component.html
@@ -47,7 +47,7 @@
-
+
{{ "excludedDomains" | i18n }}
diff --git a/apps/browser/src/platform/popup/layout/popup-page.component.html b/apps/browser/src/platform/popup/layout/popup-page.component.html
index 94f0846a852..2313b942a38 100644
--- a/apps/browser/src/platform/popup/layout/popup-page.component.html
+++ b/apps/browser/src/platform/popup/layout/popup-page.component.html
@@ -19,7 +19,7 @@
[ngClass]="{ 'tw-invisible': loading }"
>
diff --git a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
index 322fea94e3a..b9edf8eebcc 100644
--- a/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
+++ b/libs/tools/send/send-ui/src/send-form/components/options/send-options.component.html
@@ -1,4 +1,4 @@
-
+
{{ "additionalOptions" | i18n }}
diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
index 06b0f1a55df..214c978ad48 100644
--- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
+++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.html
@@ -1,4 +1,4 @@
-
+
{{ "sendDetails" | i18n }}
diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
index d244be15087..4b0e5970606 100644
--- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
+++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html
@@ -1,4 +1,4 @@
- 0">
+ 0" disableMargin>
{{ headerText }}
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
index c00f51c8eba..e1eedb36e85 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.html
@@ -1,4 +1,7 @@
-
+
{{ "additionalOptions" | i18n }}
@@ -29,4 +32,7 @@
-
+
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
index 705c170933a..f1c8085ae15 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.spec.ts
@@ -1,4 +1,4 @@
-import { Component } from "@angular/core";
+import { Component, Input } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
@@ -18,7 +18,9 @@ import { AdditionalOptionsSectionComponent } from "./additional-options-section.
selector: "vault-custom-fields",
template: "",
})
-class MockCustomFieldsComponent {}
+class MockCustomFieldsComponent {
+ @Input() disableSectionMargin: boolean;
+}
describe("AdditionalOptionsSectionComponent", () => {
let component: AdditionalOptionsSectionComponent;
diff --git a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
index 9c619ca2f84..4d72a89e4e1 100644
--- a/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
+++ b/libs/vault/src/cipher-form/components/additional-options/additional-options-section.component.ts
@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
-import { ChangeDetectorRef, Component, OnInit, ViewChild } from "@angular/core";
+import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { shareReplay } from "rxjs";
@@ -59,6 +59,8 @@ export class AdditionalOptionsSectionComponent implements OnInit {
/** True when the form is in `partial-edit` mode */
isPartialEdit = false;
+ @Input() disableSectionMargin: boolean;
+
constructor(
private cipherFormContainer: CipherFormContainer,
private formBuilder: FormBuilder,
diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.html b/libs/vault/src/cipher-form/components/cipher-form.component.html
index 2644741385b..6b327486c47 100644
--- a/libs/vault/src/cipher-form/components/cipher-form.component.html
+++ b/libs/vault/src/cipher-form/components/cipher-form.component.html
@@ -28,7 +28,9 @@
[originalCipherView]="originalCipherView"
>
-
+
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 c7c5f4a930e..3bce3c5f385 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html
@@ -1,4 +1,4 @@
-
+
{{ "customFields" | i18n }}
diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
index bdb96f4327d..083615798b6 100644
--- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
+++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.ts
@@ -11,6 +11,7 @@ import {
ElementRef,
EventEmitter,
inject,
+ Input,
OnInit,
Output,
QueryList,
@@ -94,6 +95,8 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
@ViewChildren("customFieldRow") customFieldRows: QueryList>;
+ @Input() disableSectionMargin: boolean;
+
customFieldsForm = this.formBuilder.group({
fields: new FormArray([]),
});
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 c069e36dde1..9395fb34fc7 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
@@ -1,4 +1,4 @@
-
+
{{ "itemHistory" | i18n }}
From e60ea3497ac3fa90264b2a2d8bf10463dd0bc8bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Wed, 26 Mar 2025 18:25:32 -0400
Subject: [PATCH 20/66] [PM-19571] do not import ssh keys when rehydrating
exported data (#14012)
---
libs/common/src/models/export/ssh-key.export.ts | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 5387daf7dd0..15cd3015160 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -26,10 +26,9 @@ export class SshKeyExport {
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- const parsedKey = import_ssh_key(req.privateKey);
- domain.privateKey = new EncString(parsedKey.privateKey);
- domain.publicKey = new EncString(parsedKey.publicKey);
- domain.keyFingerprint = new EncString(parsedKey.fingerprint);
+ domain.privateKey = new EncString(req.privateKey);
+ domain.publicKey = new EncString(req.publicKey);
+ domain.keyFingerprint = new EncString(req.keyFingerprint);
return domain;
}
From 9eeeac24d4ac6b288dea2b43d2432080f244b34a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Wed, 26 Mar 2025 18:37:24 -0400
Subject: [PATCH 21/66] =?UTF-8?q?Revert=20"[PM-19571]=20do=20not=20import?=
=?UTF-8?q?=20ssh=20keys=20when=20rehydrating=20exported=20data=20(#14?=
=?UTF-8?q?=E2=80=A6"=20(#14013)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts commit e60ea3497ac3fa90264b2a2d8bf10463dd0bc8bd.
---
libs/common/src/models/export/ssh-key.export.ts | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 15cd3015160..5387daf7dd0 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -26,9 +26,10 @@ export class SshKeyExport {
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- domain.privateKey = new EncString(req.privateKey);
- domain.publicKey = new EncString(req.publicKey);
- domain.keyFingerprint = new EncString(req.keyFingerprint);
+ const parsedKey = import_ssh_key(req.privateKey);
+ domain.privateKey = new EncString(parsedKey.privateKey);
+ domain.publicKey = new EncString(parsedKey.publicKey);
+ domain.keyFingerprint = new EncString(parsedKey.fingerprint);
return domain;
}
From 07245d790f1eee23b8ae97e477ba1877199f843c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?=
Date: Thu, 27 Mar 2025 09:32:10 -0400
Subject: [PATCH 22/66] [PM-19571] do not import ssh keys when rehydrating
exported data (#14014)
* do not import ssh keys when rehydrating exported data
* Remove import_ssh_key sdk function from toView
---------
Co-authored-by: Daniel James Smith
---
libs/common/src/models/export/ssh-key.export.ts | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
index 5387daf7dd0..e82cb38b6d4 100644
--- a/libs/common/src/models/export/ssh-key.export.ts
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -1,6 +1,5 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
-import { import_ssh_key } from "@bitwarden/sdk-internal";
import { EncString } from "../../platform/models/domain/enc-string";
import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key";
@@ -18,18 +17,16 @@ export class SshKeyExport {
}
static toView(req: SshKeyExport, view = new SshKeyView()) {
- const parsedKey = import_ssh_key(req.privateKey);
- view.privateKey = parsedKey.privateKey;
- view.publicKey = parsedKey.publicKey;
- view.keyFingerprint = parsedKey.fingerprint;
+ view.privateKey = req.privateKey;
+ view.publicKey = req.publicKey;
+ view.keyFingerprint = req.keyFingerprint;
return view;
}
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
- const parsedKey = import_ssh_key(req.privateKey);
- domain.privateKey = new EncString(parsedKey.privateKey);
- domain.publicKey = new EncString(parsedKey.publicKey);
- domain.keyFingerprint = new EncString(parsedKey.fingerprint);
+ domain.privateKey = new EncString(req.privateKey);
+ domain.publicKey = new EncString(req.publicKey);
+ domain.keyFingerprint = new EncString(req.keyFingerprint);
return domain;
}
From a80d289b1bfe582cbf84b98de90c37e813c83cba Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Thu, 27 Mar 2025 15:10:06 +0100
Subject: [PATCH 23/66] [PM-17809] Fix org-owned ssh showing up in ssh list
request (#13175)
* Fix org keys showing up in list request
* Change from tripleequal to double equal
---
apps/desktop/src/autofill/services/ssh-agent.service.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/autofill/services/ssh-agent.service.ts b/apps/desktop/src/autofill/services/ssh-agent.service.ts
index bf7167c0240..6522ef19500 100644
--- a/apps/desktop/src/autofill/services/ssh-agent.service.ts
+++ b/apps/desktop/src/autofill/services/ssh-agent.service.ts
@@ -143,7 +143,10 @@ export class SshAgentService implements OnDestroy {
if (isListRequest) {
const sshCiphers = ciphers.filter(
- (cipher) => cipher.type === CipherType.SshKey && !cipher.isDeleted,
+ (cipher) =>
+ cipher.type === CipherType.SshKey &&
+ !cipher.isDeleted &&
+ cipher.organizationId == null,
);
const keys = sshCiphers.map((cipher) => {
return {
@@ -247,7 +250,7 @@ export class SshAgentService implements OnDestroy {
(cipher) =>
cipher.type === CipherType.SshKey &&
!cipher.isDeleted &&
- cipher.organizationId === null,
+ cipher.organizationId == null,
);
const keys = sshCiphers.map((cipher) => {
return {
From 1887b75c773441b1c77ea1043f4388de4a2f71e9 Mon Sep 17 00:00:00 2001
From: Brandon Treston
Date: Thu, 27 Mar 2025 10:17:11 -0400
Subject: [PATCH 24/66] fix width (#14005)
---
.../organizations/collections/vault.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.html b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
index 65cd26bafee..604d326bf37 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault.component.html
+++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.html
@@ -54,7 +54,7 @@
(searchTextChanged)="filterSearchText($event)"
>
-
+
Date: Thu, 27 Mar 2025 10:33:16 -0400
Subject: [PATCH 25/66] [PM-12035] Vault filter updates to use SingleUserState
(#13641)
* vault filter use SingleUserState
* fixing tests
* Changes so that userId is passed to service, instead of access in service
* passing activeUserId from the components to service
* Sugggested changes
* updating functions to be abstract on vault-filter.service
* updating all functions to be abstract on vault filter service
---
.../vault-filter/vault-filter.component.ts | 4 +--
.../components/vault-filter.component.ts | 4 ++-
.../abstractions/vault-filter.service.ts | 20 ++++++++------
.../services/vault-filter.service.spec.ts | 18 ++++++-------
.../services/vault-filter.service.ts | 27 +++++++++++--------
.../vault-filter-section.component.ts | 9 +++++--
6 files changed, 49 insertions(+), 33 deletions(-)
diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
index 43d8f910d0f..384390d738e 100644
--- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
+++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts
@@ -89,8 +89,8 @@ export class VaultFilterComponent
const collapsedNodes = await firstValueFrom(this.vaultFilterService.collapsedFilterNodes$);
collapsedNodes.delete("AllCollections");
-
- await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes);
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.setCollapsedFilterNodes(collapsedNodes, userId);
}
protected async addCollectionFilter(): Promise {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
index 0a168157705..3f1e7755c8f 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts
@@ -94,6 +94,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
}
private trialFlowService = inject(TrialFlowService);
+ protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
constructor(
protected vaultFilterService: VaultFilterService,
@@ -162,7 +163,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy {
filter.selectedOrganizationNode = orgNode;
}
this.vaultFilterService.setOrganizationFilter(orgNode.node);
- await this.vaultFilterService.expandOrgFilter();
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.expandOrgFilter(userId);
};
applyTypeFilter = async (filterNode: TreeNode): Promise => {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
index b8494c8aa54..0e3ee69a2c6 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/abstractions/vault-filter.service.ts
@@ -4,6 +4,7 @@ import { Observable } from "rxjs";
import { CollectionAdminView, CollectionView } from "@bitwarden/admin-console/common";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
+import { UserId } from "@bitwarden/common/types/guid";
import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
@@ -22,16 +23,19 @@ export abstract class VaultFilterService {
folderTree$: Observable>;
collectionTree$: Observable>;
cipherTypeTree$: Observable>;
- getCollectionNodeFromTree: (id: string) => Promise>;
- setCollapsedFilterNodes: (collapsedFilterNodes: Set) => Promise;
- expandOrgFilter: () => Promise;
- getOrganizationFilter: () => Observable;
- setOrganizationFilter: (organization: Organization) => void;
- buildTypeTree: (
+ abstract getCollectionNodeFromTree: (id: string) => Promise>;
+ abstract setCollapsedFilterNodes: (
+ collapsedFilterNodes: Set,
+ userId: UserId,
+ ) => Promise;
+ abstract expandOrgFilter: (userId: UserId) => Promise;
+ abstract getOrganizationFilter: () => Observable;
+ abstract setOrganizationFilter: (organization: Organization) => void;
+ abstract buildTypeTree: (
head: CipherTypeFilter,
array: CipherTypeFilter[],
) => Observable>;
// TODO: Remove this from org vault when collection admin service adopts state management
- reloadCollections?: (collections: CollectionAdminView[]) => void;
- clearOrganizationFilter: () => void;
+ abstract reloadCollections?: (collections: CollectionAdminView[]) => void;
+ abstract clearOrganizationFilter: () => void;
}
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
index f56931fa987..559d0cc60c5 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts
@@ -2,7 +2,7 @@ import {
FakeAccountService,
mockAccountServiceWith,
} from "@bitwarden/common/../spec/fake-account-service";
-import { FakeActiveUserState } from "@bitwarden/common/../spec/fake-state";
+import { FakeSingleUserState } from "@bitwarden/common/../spec/fake-state";
import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider";
import { mock, MockProxy } from "jest-mock-extended";
import { firstValueFrom, ReplaySubject } from "rxjs";
@@ -42,7 +42,7 @@ describe("vault filter service", () => {
const mockUserId = Utils.newGuid() as UserId;
let accountService: FakeAccountService;
- let collapsedGroupingsState: FakeActiveUserState;
+ let collapsedGroupingsState: FakeSingleUserState;
beforeEach(() => {
organizationService = mock();
@@ -83,21 +83,21 @@ describe("vault filter service", () => {
collectionService,
accountService,
);
- collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
+ collapsedGroupingsState = stateProvider.singleUser.getFake(mockUserId, COLLAPSED_GROUPINGS);
});
describe("collapsed filter nodes", () => {
const nodes = new Set(["1", "2"]);
it("should update the collapsedFilterNodes$", async () => {
- await vaultFilterService.setCollapsedFilterNodes(nodes);
+ await vaultFilterService.setCollapsedFilterNodes(nodes, mockUserId);
- const collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS);
- expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
- expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith([
+ const collapsedGroupingsState = stateProvider.singleUser.getFake(
mockUserId,
- Array.from(nodes),
- ]);
+ COLLAPSED_GROUPINGS,
+ );
+ expect(await firstValueFrom(collapsedGroupingsState.state$)).toEqual(Array.from(nodes));
+ expect(collapsedGroupingsState.nextMock).toHaveBeenCalledWith(Array.from(nodes));
});
it("loads from state on initialization", async () => {
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
index f3e4441af9f..058c84517cb 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts
@@ -23,8 +23,10 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
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 { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
+import { SingleUserState, StateProvider } from "@bitwarden/common/platform/state";
+import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { CipherType } from "@bitwarden/common/vault/enums";
@@ -47,12 +49,17 @@ const NestingDelimiter = "/";
@Injectable()
export class VaultFilterService implements VaultFilterServiceAbstraction {
- private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id));
+ protected activeUserId$ = this.accountService.activeAccount$.pipe(getUserId);
memberOrganizations$ = this.activeUserId$.pipe(
switchMap((id) => this.organizationService.memberOrganizations$(id)),
);
+ collapsedFilterNodes$ = this.activeUserId$.pipe(
+ switchMap((id) => this.collapsedGroupingsState(id).state$),
+ map((state) => new Set(state)),
+ );
+
organizationTree$: Observable> = combineLatest([
this.memberOrganizations$,
this.activeUserId$.pipe(
@@ -103,11 +110,9 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
cipherTypeTree$: Observable> = this.buildCipherTypeTree();
- private collapsedGroupingsState: ActiveUserState =
- this.stateProvider.getActive(COLLAPSED_GROUPINGS);
-
- readonly collapsedFilterNodes$: Observable> =
- this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c)));
+ private collapsedGroupingsState(userId: UserId): SingleUserState {
+ return this.stateProvider.getUser(userId, COLLAPSED_GROUPINGS);
+ }
constructor(
protected organizationService: OrganizationService,
@@ -125,8 +130,8 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode;
}
- async setCollapsedFilterNodes(collapsedFilterNodes: Set): Promise {
- await this.collapsedGroupingsState.update(() => Array.from(collapsedFilterNodes));
+ async setCollapsedFilterNodes(collapsedFilterNodes: Set, userId: UserId): Promise {
+ await this.collapsedGroupingsState(userId).update(() => Array.from(collapsedFilterNodes));
}
protected async getCollapsedFilterNodes(): Promise> {
@@ -149,13 +154,13 @@ export class VaultFilterService implements VaultFilterServiceAbstraction {
}
}
- async expandOrgFilter() {
+ async expandOrgFilter(userId: UserId) {
const collapsedFilterNodes = await firstValueFrom(this.collapsedFilterNodes$);
if (!collapsedFilterNodes.has("AllVaults")) {
return;
}
collapsedFilterNodes.delete("AllVaults");
- await this.setCollapsedFilterNodes(collapsedFilterNodes);
+ await this.setCollapsedFilterNodes(collapsedFilterNodes, userId);
}
protected async buildOrganizationTree(
diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
index b231200a7cb..41329319805 100644
--- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts
@@ -1,10 +1,12 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, InjectionToken, Injector, Input, OnDestroy, OnInit } from "@angular/core";
-import { Observable, Subject, takeUntil } from "rxjs";
+import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs";
import { map } from "rxjs/operators";
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 { ITreeNodeObject, TreeNode } from "@bitwarden/common/vault/models/domain/tree-node";
import { VaultFilterService } from "../../services/abstractions/vault-filter.service";
@@ -17,6 +19,7 @@ import { VaultFilter } from "../models/vault-filter.model";
})
export class VaultFilterSectionComponent implements OnInit, OnDestroy {
private destroy$ = new Subject();
+ private activeUserId$ = getUserId(this.accountService.activeAccount$);
@Input() activeFilter: VaultFilter;
@Input() section: VaultFilterSection;
@@ -29,6 +32,7 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
constructor(
private vaultFilterService: VaultFilterService,
private injector: Injector,
+ private accountService: AccountService,
) {
this.vaultFilterService.collapsedFilterNodes$
.pipe(takeUntil(this.destroy$))
@@ -126,7 +130,8 @@ export class VaultFilterSectionComponent implements OnInit, OnDestroy {
} else {
this.collapsedFilterNodes.add(node.id);
}
- await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes);
+ const userId = await firstValueFrom(this.activeUserId$);
+ await this.vaultFilterService.setCollapsedFilterNodes(this.collapsedFilterNodes, userId);
}
// an injector is necessary to pass data into a dynamic component
From b6e9596c84ee83f7b77863a091c8f8d677bded1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rui=20Tom=C3=A9?=
<108268980+r-tome@users.noreply.github.com>
Date: Thu, 27 Mar 2025 14:53:32 +0000
Subject: [PATCH 26/66] [PM-18523] Make the External ID in the Group dialog
conditional (#14006)
* Add SsoExternalIdVisibility feature flag
* Implement visibility logic for Group external ID based on SsoExternalIdVisibility feature flag
---
.../organizations/manage/group-add-edit.component.html | 2 +-
.../organizations/manage/group-add-edit.component.ts | 7 +++++++
libs/common/src/enums/feature-flag.enum.ts | 2 ++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
index 9fb8b245f72..5c8c0c07f88 100644
--- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
+++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.html
@@ -23,7 +23,7 @@
{{ "characterMaximum" | i18n: 100 }}
-
+
{{ "externalId" | i18n }}
{{ "externalIdDesc" | i18n }}
diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
index 330ffe86f0b..d1a9d4919e5 100644
--- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
+++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts
@@ -29,7 +29,9 @@ import {
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -215,6 +217,10 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
this.groupDetails$,
]).pipe(map(([allowAdminAccess, groupDetails]) => !allowAdminAccess && groupDetails != null));
+ protected isExternalIdVisible$ = this.configService
+ .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility)
+ .pipe(map((isEnabled) => !isEnabled || !!this.groupForm.get("externalId")?.value));
+
constructor(
@Inject(DIALOG_DATA) private params: GroupAddEditDialogParams,
private dialogRef: DialogRef,
@@ -231,6 +237,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy {
private accountService: AccountService,
private collectionAdminService: CollectionAdminService,
private toastService: ToastService,
+ private configService: ConfigService,
) {
this.tabIndex = params.initialTab ?? GroupAddEditTabType.Info;
}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index e3c15a1d40b..c5119cd5206 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -8,6 +8,7 @@ export enum FeatureFlag {
AccountDeprovisioning = "pm-10308-account-deprovisioning",
VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint",
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
+ SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -68,6 +69,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.AccountDeprovisioning]: FALSE,
[FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE,
[FeatureFlag.LimitItemDeletion]: FALSE,
+ [FeatureFlag.SsoExternalIdVisibility]: FALSE,
/* Autofill */
[FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE,
From 96558074e2b3e01031ab32bba4fab20a08579568 Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Thu, 27 Mar 2025 12:14:16 -0400
Subject: [PATCH 27/66] Updated short name to no longer be localized (#14022)
---
apps/browser/src/manifest.json | 2 +-
apps/browser/src/manifest.v3.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json
index af2bf72b7b5..07aa3d2e4a9 100644
--- a/apps/browser/src/manifest.json
+++ b/apps/browser/src/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "__MSG_extName__",
- "short_name": "__MSG_appName__",
+ "short_name": "Bitwarden",
"version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json
index 131724e38f9..be1a3f17827 100644
--- a/apps/browser/src/manifest.v3.json
+++ b/apps/browser/src/manifest.v3.json
@@ -2,7 +2,7 @@
"manifest_version": 3,
"minimum_chrome_version": "102.0",
"name": "__MSG_extName__",
- "short_name": "__MSG_appName__",
+ "short_name": "Bitwarden",
"version": "2025.3.2",
"description": "__MSG_extDesc__",
"default_locale": "en",
From 7d3e43abd8f98ec1169f2d2b1ac148a6b7820da9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 27 Mar 2025 12:21:27 -0400
Subject: [PATCH 28/66] [deps] Autofill: Update prettier-plugin-tailwindcss to
v0.6.11 (#13225)
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 19b1772e4bc..395cf52f05a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -158,7 +158,7 @@
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"prettier": "3.4.2",
- "prettier-plugin-tailwindcss": "0.6.10",
+ "prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
@@ -30145,9 +30145,9 @@
}
},
"node_modules/prettier-plugin-tailwindcss": {
- "version": "0.6.10",
- "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.10.tgz",
- "integrity": "sha512-ndj2WLDaMzACnr1gAYZiZZLs5ZdOeBYgOsbBmHj3nvW/6q8h8PymsXiEnKvj/9qgCCAoHyvLOisoQdIcsDvIgw==",
+ "version": "0.6.11",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz",
+ "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==",
"dev": true,
"license": "MIT",
"engines": {
diff --git a/package.json b/package.json
index 943fde5fcf3..83a0aa96fa3 100644
--- a/package.json
+++ b/package.json
@@ -120,7 +120,7 @@
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"prettier": "3.4.2",
- "prettier-plugin-tailwindcss": "0.6.10",
+ "prettier-plugin-tailwindcss": "0.6.11",
"process": "0.11.10",
"remark-gfm": "4.0.0",
"rimraf": "6.0.1",
From 93a289bfa81d1ef8cc955696f03e7272e31f39ce Mon Sep 17 00:00:00 2001
From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com>
Date: Thu, 27 Mar 2025 10:46:39 -0700
Subject: [PATCH 29/66] add tests (#13923)
---
.../src/vault/services/cipher.service.spec.ts | 69 ++++++++++++++-----
.../src/vault/services/cipher.service.ts | 6 +-
2 files changed, 57 insertions(+), 18 deletions(-)
diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts
index def0c04dd16..dd7faea8e8a 100644
--- a/libs/common/src/vault/services/cipher.service.spec.ts
+++ b/libs/common/src/vault/services/cipher.service.spec.ts
@@ -305,51 +305,86 @@ describe("Cipher Service", () => {
});
describe("cipher.key", () => {
- it("is null when feature flag is false", async () => {
- configService.getFeatureFlag.mockResolvedValue(false);
-
+ beforeEach(() => {
keyService.getOrgKey.mockReturnValue(
Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
);
+ });
+
+ it("is null when feature flag is false", async () => {
+ configService.getFeatureFlag.mockResolvedValue(false);
const cipher = await cipherService.encrypt(cipherView, userId);
expect(cipher.key).toBeNull();
});
- it("is defined when feature flag flag is true", async () => {
- configService.getFeatureFlag.mockResolvedValue(true);
+ describe("when feature flag is true", () => {
+ beforeEach(() => {
+ configService.getFeatureFlag.mockResolvedValue(true);
+ });
- const cipher = await cipherService.encrypt(cipherView, userId);
+ it("is null when the cipher is not viewPassword", async () => {
+ cipherView.viewPassword = false;
- expect(cipher.key).toBeDefined();
+ const cipher = await cipherService.encrypt(cipherView, userId);
+
+ expect(cipher.key).toBeNull();
+ });
+
+ it("is defined when the cipher is viewPassword", async () => {
+ cipherView.viewPassword = true;
+
+ const cipher = await cipherService.encrypt(cipherView, userId);
+
+ expect(cipher.key).toBeDefined();
+ });
});
});
describe("encryptWithCipherKey", () => {
beforeEach(() => {
jest.spyOn(cipherService, "encryptCipherWithCipherKey");
+ keyService.getOrgKey.mockReturnValue(
+ Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
+ );
});
it("is not called when feature flag is false", async () => {
configService.getFeatureFlag.mockResolvedValue(false);
- keyService.getOrgKey.mockReturnValue(
- Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
- );
await cipherService.encrypt(cipherView, userId);
expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled();
});
- it("is called when feature flag is true", async () => {
- configService.getFeatureFlag.mockResolvedValue(true);
- keyService.getOrgKey.mockReturnValue(
- Promise.resolve(new SymmetricCryptoKey(new Uint8Array(32)) as OrgKey),
- );
+ describe("when feature flag is true", () => {
+ beforeEach(() => {
+ configService.getFeatureFlag.mockResolvedValue(true);
+ });
- await cipherService.encrypt(cipherView, userId);
+ it("is called when cipher viewPassword is true", async () => {
+ cipherView.viewPassword = true;
- expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ await cipherService.encrypt(cipherView, userId);
+
+ expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ });
+
+ it("is not called when cipher viewPassword is false and original cipher has no key", async () => {
+ cipherView.viewPassword = false;
+
+ await cipherService.encrypt(cipherView, userId, undefined, undefined, new Cipher());
+
+ expect(cipherService["encryptCipherWithCipherKey"]).not.toHaveBeenCalled();
+ });
+
+ it("is called when cipher viewPassword is false and original cipher has a key", async () => {
+ cipherView.viewPassword = false;
+
+ await cipherService.encrypt(cipherView, userId, undefined, undefined, cipherObj);
+
+ expect(cipherService["encryptCipherWithCipherKey"]).toHaveBeenCalled();
+ });
});
});
});
diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts
index 4876a755ed8..bb5ae710cfb 100644
--- a/libs/common/src/vault/services/cipher.service.ts
+++ b/libs/common/src/vault/services/cipher.service.ts
@@ -222,7 +222,11 @@ export class CipherService implements CipherServiceAbstraction {
cipher.reprompt = model.reprompt;
cipher.edit = model.edit;
- if (await this.getCipherKeyEncryptionEnabled()) {
+ if (
+ // prevent unprivileged users from migrating to cipher key encryption
+ (model.viewPassword || originalCipher?.key) &&
+ (await this.getCipherKeyEncryptionEnabled())
+ ) {
cipher.key = originalCipher?.key ?? null;
const userOrOrgKey = await this.getKeyForCipherKeyDecryption(cipher, userId);
// The keyForEncryption is only used for encrypting the cipher key, not the cipher itself, since cipher key encryption is enabled.
From 10695fd9719b0bdeac5d73f3a11b1b3a7f5bd464 Mon Sep 17 00:00:00 2001
From: bnagawiecki <107435978+bnagawiecki@users.noreply.github.com>
Date: Thu, 27 Mar 2025 14:20:34 -0400
Subject: [PATCH 30/66] add data-testids to 3 ssh key fields (#14024)
---
.../sshkey-view.component.html | 25 ++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
index ee5a94249c4..20390c0a285 100644
--- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
+++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
@@ -5,7 +5,14 @@
{{ "sshPrivateKey" | i18n }}
-
+
{{ "sshPublicKey" | i18n }}
-
+
{{ "sshFingerprint" | i18n }}
-
+
Date: Thu, 27 Mar 2025 11:57:24 -0700
Subject: [PATCH 31/66] [PM-16100][A11y][Extension] Usernames not being read by
screen readers like they used to be (#13800)
* update autofill a11y
* fixes to cipher item title
---
apps/browser/src/_locales/en/messages.json | 28 +++++++++++++++++++
.../autofill-vault-list-items.component.html | 2 +-
.../autofill-vault-list-items.component.ts | 16 ++++-------
.../vault-list-items-container.component.html | 4 ++-
.../vault-list-items-container.component.ts | 13 +++++----
.../vault-settings/vault-settings.service.ts | 7 +++--
6 files changed, 51 insertions(+), 19 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 39541aa2f8c..995b90ec661 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -4279,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4289,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
index 40f00ab4332..19f1668eba4 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html
@@ -7,6 +7,6 @@
[description]="(showEmptyAutofillTip$ | async) ? ('autofillSuggestionsTip' | i18n) : null"
showAutofillButton
[disableDescriptionMargin]="showEmptyAutofillTip$ | async"
- [primaryActionAutofill]="clickItemsToAutofillVaultView"
+ [primaryActionAutofill]="clickItemsToAutofillVaultView$ | async"
[groupByType]="groupByType()"
>
diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
index 03d84120785..72d51776f7b 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.ts
@@ -1,7 +1,7 @@
import { CommonModule } from "@angular/common";
-import { Component, OnInit } from "@angular/core";
+import { Component } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
-import { combineLatest, firstValueFrom, map, Observable } from "rxjs";
+import { combineLatest, map, Observable } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
@@ -33,7 +33,7 @@ import { VaultListItemsContainerComponent } from "../vault-list-items-container/
selector: "app-autofill-vault-list-items",
templateUrl: "autofill-vault-list-items.component.html",
})
-export class AutofillVaultListItemsComponent implements OnInit {
+export class AutofillVaultListItemsComponent {
/**
* The list of ciphers that can be used to autofill the current page.
* @protected
@@ -47,7 +47,9 @@ export class AutofillVaultListItemsComponent implements OnInit {
*/
protected showRefresh: boolean = BrowserPopupUtils.inSidebar(window);
- clickItemsToAutofillVaultView = false;
+ /** Flag indicating whether the login item should automatically autofill when clicked */
+ protected clickItemsToAutofillVaultView$: Observable =
+ this.vaultSettingsService.clickItemsToAutofillVaultView$;
protected groupByType = toSignal(
this.vaultPopupItemsService.hasFilterApplied$.pipe(map((hasFilter) => !hasFilter)),
@@ -84,12 +86,6 @@ export class AutofillVaultListItemsComponent implements OnInit {
// TODO: Migrate logic to show Autofill policy toast PM-8144
}
- async ngOnInit() {
- this.clickItemsToAutofillVaultView = await firstValueFrom(
- this.vaultSettingsService.clickItemsToAutofillVaultView$,
- );
- }
-
/**
* Refreshes the current tab to re-populate the autofill ciphers.
* @protected
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 cbce4bf2961..a55bba622e4 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
@@ -99,7 +99,9 @@
type="button"
(click)="primaryActionOnSelect(cipher)"
(dblclick)="launchCipher(cipher)"
- [appA11yTitle]="cipherItemTitleKey | async | i18n: cipher.name"
+ [appA11yTitle]="
+ cipherItemTitleKey(cipher) | async | i18n: cipher.name : cipher.login.username
+ "
class="{{ itemHeightClass }}"
>
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
index 9d70c0ba236..6df1bdf8ae5 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts
@@ -206,11 +206,14 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit {
/**
* Resolved i18n key to use for suggested cipher items
*/
- cipherItemTitleKey = this.currentURIIsBlocked$.pipe(
- map((uriIsBlocked) =>
- this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle",
- ),
- );
+ cipherItemTitleKey = (cipher: CipherView) =>
+ this.currentURIIsBlocked$.pipe(
+ map((uriIsBlocked) => {
+ const hasUsername = cipher.login?.username != null;
+ const key = this.primaryActionAutofill && !uriIsBlocked ? "autofillTitle" : "viewItemTitle";
+ return hasUsername ? `${key}WithField` : key;
+ }),
+ );
/**
* Option to show the autofill button for each item.
diff --git a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
index 85ab3914158..423acba8fff 100644
--- a/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
+++ b/libs/common/src/vault/services/vault-settings/vault-settings.service.ts
@@ -1,4 +1,4 @@
-import { Observable, map } from "rxjs";
+import { Observable, map, shareReplay } from "rxjs";
import { ActiveUserState, GlobalState, StateProvider } from "../../../platform/state";
import { VaultSettingsService as VaultSettingsServiceAbstraction } from "../../abstractions/vault-settings/vault-settings.service";
@@ -46,7 +46,10 @@ export class VaultSettingsService implements VaultSettingsServiceAbstraction {
* {@link VaultSettingsServiceAbstraction.clickItemsToAutofillVaultView$$}
*/
readonly clickItemsToAutofillVaultView$: Observable
=
- this.clickItemsToAutofillVaultViewState.state$.pipe(map((x) => x ?? false));
+ this.clickItemsToAutofillVaultViewState.state$.pipe(
+ map((x) => x ?? false),
+ shareReplay({ bufferSize: 1, refCount: false }),
+ );
constructor(private stateProvider: StateProvider) {}
From 79d3d41a2f37a5744c5e03106e067d938013b31f Mon Sep 17 00:00:00 2001
From: Jonathan Prusik
Date: Thu, 27 Mar 2025 17:42:14 -0400
Subject: [PATCH 32/66] [PM-14052] Add lit signals dependency (#14032)
* add lit signals dependency
* add @lit-labs/signals to autofill team ownership
---
.github/renovate.json5 | 1 +
package-lock.json | 19 +++++++++++++++++++
package.json | 1 +
3 files changed, 21 insertions(+)
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 01361a97404..74c4ceed948 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -175,6 +175,7 @@
"del",
"ed25519",
"lit",
+ "@lit-labs/signals",
"patch-package",
"pkcs8",
"prettier",
diff --git a/package-lock.json b/package-lock.json
index 395cf52f05a..afe2997f0d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -83,6 +83,7 @@
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
+ "@lit-labs/signals": "0.1.2",
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
@@ -7869,6 +7870,17 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/@lit-labs/signals": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/@lit-labs/signals/-/signals-0.1.2.tgz",
+ "integrity": "sha512-hkOL0ua4ILeHlaJ8IqFKS+Y+dpYznWaDhdikzwt3zJ1/LPz3Etft4OPIMoltzbBJS5pyXPRseD/uWRlET3ImEA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "lit": "^2.0.0 || ^3.0.0",
+ "signal-polyfill": "^0.2.0"
+ }
+ },
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
@@ -32315,6 +32327,13 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/signal-polyfill": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/signal-polyfill/-/signal-polyfill-0.2.2.tgz",
+ "integrity": "sha512-p63Y4Er5/eMQ9RHg0M0Y64NlsQKpiu6MDdhBXpyywRuWiPywhJTpKJ1iB5K2hJEbFZ0BnDS7ZkJ+0AfTuL37Rg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/sigstore": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz",
diff --git a/package.json b/package.json
index 83a0aa96fa3..051ff4f7251 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"@compodoc/compodoc": "1.1.26",
"@electron/notarize": "2.5.0",
"@electron/rebuild": "3.7.1",
+ "@lit-labs/signals": "0.1.2",
"@ngtools/webpack": "18.2.12",
"@storybook/addon-a11y": "8.5.2",
"@storybook/addon-actions": "8.5.2",
From cfafeaac66820db6fdd8030f37b90bc87948120c Mon Sep 17 00:00:00 2001
From: Matt Bishop
Date: Thu, 27 Mar 2025 15:46:48 -0700
Subject: [PATCH 33/66] Remove references to Codecov token (#14033)
---
.github/workflows/test.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0b039315b30..6411337f6e9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -71,8 +71,6 @@ jobs:
- name: Upload results to codecov.io
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
rust:
name: Run Rust tests on ${{ matrix.os }}
From 8dead9baf6f82c279394bf479d8599d652e299c4 Mon Sep 17 00:00:00 2001
From: Shane Melton
Date: Thu, 27 Mar 2025 17:01:58 -0700
Subject: [PATCH 34/66] [PM-14426] At-risk password carousel fixes (#13936)
* [PM-19004] Add period to image alt text
* [PM-19298] Add data-testid for carousel confirmation button
---
apps/browser/src/_locales/en/messages.json | 12 ++++++------
.../at-risk-carousel-dialog.component.html | 7 ++++---
2 files changed, 10 insertions(+), 9 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 995b90ec661..2c940ccdf5a 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2534,15 +2534,15 @@
"message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
- "reviewAtRiskLoginSlideImgAlt": {
- "message": "Illustration of a list of logins that are at-risk"
+ "reviewAtRiskLoginSlideImgAltPeriod": {
+ "message": "Illustration of a list of logins that are at-risk."
},
"generatePasswordSlideDesc": {
"message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
"description": "Description of the generate password slide on the at-risk password page carousel"
},
- "generatePasswordSlideImgAlt": {
- "message": "Illustration of the Bitwarden autofill menu displaying a generated password"
+ "generatePasswordSlideImgAltPeriod": {
+ "message": "Illustration of the Bitwarden autofill menu displaying a generated password."
},
"updateInBitwarden": {
"message": "Update in Bitwarden"
@@ -2551,8 +2551,8 @@
"message": "Bitwarden will then prompt you to update the password in the password manager.",
"description": "Description of the update in Bitwarden slide on the at-risk password page carousel"
},
- "updateInBitwardenSlideImgAlt": {
- "message": "Illustration of a Bitwarden’s notification prompting the user to update the login"
+ "updateInBitwardenSlideImgAltPeriod": {
+ "message": "Illustration of a Bitwarden’s notification prompting the user to update the login."
},
"turnOnAutofill": {
"message": "Turn on autofill"
diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
index c5e3ea8fb0d..e63b06750c8 100644
--- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
+++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.html
@@ -6,7 +6,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/review_at-risk_logins.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/review_at-risk_logins.dark.png"
- [alt]="'reviewAtRiskLoginSlideImgAlt' | i18n"
+ [alt]="'reviewAtRiskLoginSlideImgAltPeriod' | i18n"
/>
{{ "reviewAtRiskLogins" | i18n }}
@@ -18,7 +18,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/generate_password.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/generate_password.dark.png"
- [alt]="'generatePasswordSlideImgAlt' | i18n"
+ [alt]="'generatePasswordSlideImgAltPeriod' | i18n"
/>
{{ "generatePassword" | i18n }}
@@ -30,7 +30,7 @@
class="tw-max-w-full tw-max-h-40"
src="../../../../images/at-risk-password-carousel/update_login.light.png"
appDarkImgSrc="../../../../images/at-risk-password-carousel/update_login.dark.png"
- [alt]="'updateInBitwardenSlideImgAlt' | i18n"
+ [alt]="'updateInBitwardenSlideImgAltPeriod' | i18n"
/>
{{ "updateInBitwarden" | i18n }}
@@ -47,6 +47,7 @@
block
[disabled]="!dismissBtnEnabled()"
(click)="dismiss()"
+ data-testid="confirm-carousel-button"
>
{{ "reviewAtRiskPasswords" | i18n }}
From 6d4179052a318655f26de0408c82fe0563ef5295 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:22:38 +0100
Subject: [PATCH 35/66] Autosync the updated translations (#14038)
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 | 9 +++++++++
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 | 9 +++++++++
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 | 9 +++++++++
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 | 9 +++++++++
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 | 11 ++++++++++-
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 | 11 ++++++++++-
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 | 9 +++++++++
apps/desktop/src/locales/pt_PT/messages.json | 9 +++++++++
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 | 9 +++++++++
apps/desktop/src/locales/sl/messages.json | 9 +++++++++
apps/desktop/src/locales/sr/messages.json | 9 +++++++++
apps/desktop/src/locales/sv/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 | 9 +++++++++
apps/desktop/src/locales/zh_TW/messages.json | 9 +++++++++
63 files changed, 569 insertions(+), 2 deletions(-)
diff --git a/apps/desktop/src/locales/af/messages.json b/apps/desktop/src/locales/af/messages.json
index ae324eda6bc..e288e9e70cc 100644
--- a/apps/desktop/src/locales/af/messages.json
+++ b/apps/desktop/src/locales/af/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ar/messages.json b/apps/desktop/src/locales/ar/messages.json
index 9f5b1f3501c..7de8ecb50f5 100644
--- a/apps/desktop/src/locales/ar/messages.json
+++ b/apps/desktop/src/locales/ar/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "تصدير خزانة المؤسسة"
},
diff --git a/apps/desktop/src/locales/az/messages.json b/apps/desktop/src/locales/az/messages.json
index b7c7cb3cd67..738944f1791 100644
--- a/apps/desktop/src/locales/az/messages.json
+++ b/apps/desktop/src/locales/az/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Təşkilat seyfini xaricə köçürmə"
},
diff --git a/apps/desktop/src/locales/be/messages.json b/apps/desktop/src/locales/be/messages.json
index 4c90222be42..ef73dcb3ed3 100644
--- a/apps/desktop/src/locales/be/messages.json
+++ b/apps/desktop/src/locales/be/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/bg/messages.json b/apps/desktop/src/locales/bg/messages.json
index 6a1c0983278..c9bae830eff 100644
--- a/apps/desktop/src/locales/bg/messages.json
+++ b/apps/desktop/src/locales/bg/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Изнасяне на трезора на организацията"
},
diff --git a/apps/desktop/src/locales/bn/messages.json b/apps/desktop/src/locales/bn/messages.json
index 68848c9ef2a..440462cf5d3 100644
--- a/apps/desktop/src/locales/bn/messages.json
+++ b/apps/desktop/src/locales/bn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/bs/messages.json b/apps/desktop/src/locales/bs/messages.json
index 885af404f95..4fb9697a2c9 100644
--- a/apps/desktop/src/locales/bs/messages.json
+++ b/apps/desktop/src/locales/bs/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ca/messages.json b/apps/desktop/src/locales/ca/messages.json
index 5707fd0c0ff..dbba24a9050 100644
--- a/apps/desktop/src/locales/ca/messages.json
+++ b/apps/desktop/src/locales/ca/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "S'està exportant la caixa forta de l’organització"
},
diff --git a/apps/desktop/src/locales/cs/messages.json b/apps/desktop/src/locales/cs/messages.json
index 336ea2a795c..c4d61da936b 100644
--- a/apps/desktop/src/locales/cs/messages.json
+++ b/apps/desktop/src/locales/cs/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportování trezoru organizace"
},
diff --git a/apps/desktop/src/locales/cy/messages.json b/apps/desktop/src/locales/cy/messages.json
index 1c6d824c22e..5f06fd762f4 100644
--- a/apps/desktop/src/locales/cy/messages.json
+++ b/apps/desktop/src/locales/cy/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/da/messages.json b/apps/desktop/src/locales/da/messages.json
index ef5b22e33f4..e11aef18e14 100644
--- a/apps/desktop/src/locales/da/messages.json
+++ b/apps/desktop/src/locales/da/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksport af organisationsboks"
},
diff --git a/apps/desktop/src/locales/de/messages.json b/apps/desktop/src/locales/de/messages.json
index 79f61475ab0..b28c2fced1d 100644
--- a/apps/desktop/src/locales/de/messages.json
+++ b/apps/desktop/src/locales/de/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Tresor der Organisation wird exportiert"
},
diff --git a/apps/desktop/src/locales/el/messages.json b/apps/desktop/src/locales/el/messages.json
index ecee8bd9894..9c59be2e0a7 100644
--- a/apps/desktop/src/locales/el/messages.json
+++ b/apps/desktop/src/locales/el/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Εξαγωγή θησαυ/κίου οργανισμού"
},
diff --git a/apps/desktop/src/locales/en_GB/messages.json b/apps/desktop/src/locales/en_GB/messages.json
index f70a70e975a..74e43b24076 100644
--- a/apps/desktop/src/locales/en_GB/messages.json
+++ b/apps/desktop/src/locales/en_GB/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
diff --git a/apps/desktop/src/locales/en_IN/messages.json b/apps/desktop/src/locales/en_IN/messages.json
index f91a7b21876..3ec9e17769b 100644
--- a/apps/desktop/src/locales/en_IN/messages.json
+++ b/apps/desktop/src/locales/en_IN/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
diff --git a/apps/desktop/src/locales/eo/messages.json b/apps/desktop/src/locales/eo/messages.json
index 200c3f4a1de..2dfdcdd9484 100644
--- a/apps/desktop/src/locales/eo/messages.json
+++ b/apps/desktop/src/locales/eo/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/es/messages.json b/apps/desktop/src/locales/es/messages.json
index 665721beb3a..6936753ebb2 100644
--- a/apps/desktop/src/locales/es/messages.json
+++ b/apps/desktop/src/locales/es/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando caja fuerte de la organización"
},
diff --git a/apps/desktop/src/locales/et/messages.json b/apps/desktop/src/locales/et/messages.json
index c4ca9bef886..20650145478 100644
--- a/apps/desktop/src/locales/et/messages.json
+++ b/apps/desktop/src/locales/et/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Ekspordin organisatsiooni hoidlat"
},
diff --git a/apps/desktop/src/locales/eu/messages.json b/apps/desktop/src/locales/eu/messages.json
index 1abc24612ca..57763cc62c1 100644
--- a/apps/desktop/src/locales/eu/messages.json
+++ b/apps/desktop/src/locales/eu/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fa/messages.json b/apps/desktop/src/locales/fa/messages.json
index 2d13b86c4a8..eecbd6dcb85 100644
--- a/apps/desktop/src/locales/fa/messages.json
+++ b/apps/desktop/src/locales/fa/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fi/messages.json b/apps/desktop/src/locales/fi/messages.json
index 2da2b7dcf7a..8964e7cf982 100644
--- a/apps/desktop/src/locales/fi/messages.json
+++ b/apps/desktop/src/locales/fi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisaation holvin vienti"
},
diff --git a/apps/desktop/src/locales/fil/messages.json b/apps/desktop/src/locales/fil/messages.json
index d54c5a21a87..ec1a2d0e281 100644
--- a/apps/desktop/src/locales/fil/messages.json
+++ b/apps/desktop/src/locales/fil/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/fr/messages.json b/apps/desktop/src/locales/fr/messages.json
index 431a70ef373..750b91adf57 100644
--- a/apps/desktop/src/locales/fr/messages.json
+++ b/apps/desktop/src/locales/fr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Export du coffre-fort de l'organisation"
},
diff --git a/apps/desktop/src/locales/gl/messages.json b/apps/desktop/src/locales/gl/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/gl/messages.json
+++ b/apps/desktop/src/locales/gl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/he/messages.json b/apps/desktop/src/locales/he/messages.json
index 7f98ab977ae..34db4dd53ba 100644
--- a/apps/desktop/src/locales/he/messages.json
+++ b/apps/desktop/src/locales/he/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "מייצא כספת ארגון"
},
diff --git a/apps/desktop/src/locales/hi/messages.json b/apps/desktop/src/locales/hi/messages.json
index 90fed49de76..7cfbb08a7d4 100644
--- a/apps/desktop/src/locales/hi/messages.json
+++ b/apps/desktop/src/locales/hi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/hr/messages.json b/apps/desktop/src/locales/hr/messages.json
index cb66437bedd..989faae1759 100644
--- a/apps/desktop/src/locales/hr/messages.json
+++ b/apps/desktop/src/locales/hr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izvoz organizacijskog trezora"
},
diff --git a/apps/desktop/src/locales/hu/messages.json b/apps/desktop/src/locales/hu/messages.json
index b9573428136..775ce2dd80c 100644
--- a/apps/desktop/src/locales/hu/messages.json
+++ b/apps/desktop/src/locales/hu/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Szervezeti széf exportálása"
},
diff --git a/apps/desktop/src/locales/id/messages.json b/apps/desktop/src/locales/id/messages.json
index df9b6484778..0c46ff5e5ea 100644
--- a/apps/desktop/src/locales/id/messages.json
+++ b/apps/desktop/src/locales/id/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/it/messages.json b/apps/desktop/src/locales/it/messages.json
index ef5fa5ce381..f97b3135c9f 100644
--- a/apps/desktop/src/locales/it/messages.json
+++ b/apps/desktop/src/locales/it/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Esportando cassaforte dell'organizzazione"
},
diff --git a/apps/desktop/src/locales/ja/messages.json b/apps/desktop/src/locales/ja/messages.json
index e8ba0e8509b..58c4d866e42 100644
--- a/apps/desktop/src/locales/ja/messages.json
+++ b/apps/desktop/src/locales/ja/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "組織保管庫のエクスポート"
},
@@ -3539,7 +3548,7 @@
"message": "後で再通知"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "新しいメールアドレス $EMAIL$ はあなたが管理しているものですか?",
+ "message": "メールアドレス $EMAIL$ は、確実にアクセスできるものですか?",
"placeholders": {
"email": {
"content": "$1",
diff --git a/apps/desktop/src/locales/ka/messages.json b/apps/desktop/src/locales/ka/messages.json
index 96962e14ff5..3eb666c7d44 100644
--- a/apps/desktop/src/locales/ka/messages.json
+++ b/apps/desktop/src/locales/ka/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/km/messages.json b/apps/desktop/src/locales/km/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/km/messages.json
+++ b/apps/desktop/src/locales/km/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/kn/messages.json b/apps/desktop/src/locales/kn/messages.json
index f9bb154b66e..c530ab799f7 100644
--- a/apps/desktop/src/locales/kn/messages.json
+++ b/apps/desktop/src/locales/kn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ko/messages.json b/apps/desktop/src/locales/ko/messages.json
index 44bc681f205..e5df2555531 100644
--- a/apps/desktop/src/locales/ko/messages.json
+++ b/apps/desktop/src/locales/ko/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/lt/messages.json b/apps/desktop/src/locales/lt/messages.json
index 40436c7ccda..7cf1bc463b3 100644
--- a/apps/desktop/src/locales/lt/messages.json
+++ b/apps/desktop/src/locales/lt/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/lv/messages.json b/apps/desktop/src/locales/lv/messages.json
index c519987fc72..b3fa396d811 100644
--- a/apps/desktop/src/locales/lv/messages.json
+++ b/apps/desktop/src/locales/lv/messages.json
@@ -1706,7 +1706,7 @@
"message": "Apstiprināt glabātavas satura izgūšanu"
},
"exportWarningDesc": {
- "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izdoto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas."
+ "message": "Šī izguve satur glabātavas datus nešifrētā veidā. Izgūto datni nevajadzētu glabāt vai sūtīt nedrošos veidos (piemēram, e-pastā). Tā ir jāizdzēš uzreiz pēc izmantošanas."
},
"encExportKeyWarningDesc": {
"message": "Šī izguve šifrē datus ar konta šifrēšanas atslēgu. Ja tā jebkad tiks mainīta, izvadi vajadzētu veikt vēlreiz, jo vairs nebūs iespējams atšifrēt šo datni."
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izgūst apvienības glabātavu"
},
diff --git a/apps/desktop/src/locales/me/messages.json b/apps/desktop/src/locales/me/messages.json
index ea8defdc07c..99944c6e10c 100644
--- a/apps/desktop/src/locales/me/messages.json
+++ b/apps/desktop/src/locales/me/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ml/messages.json b/apps/desktop/src/locales/ml/messages.json
index a4af9251191..79d36b99526 100644
--- a/apps/desktop/src/locales/ml/messages.json
+++ b/apps/desktop/src/locales/ml/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/mr/messages.json b/apps/desktop/src/locales/mr/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/mr/messages.json
+++ b/apps/desktop/src/locales/mr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/my/messages.json b/apps/desktop/src/locales/my/messages.json
index 493d6fdb5b9..8aa0327203b 100644
--- a/apps/desktop/src/locales/my/messages.json
+++ b/apps/desktop/src/locales/my/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/nb/messages.json b/apps/desktop/src/locales/nb/messages.json
index caf4d3da64b..d96eb92efb2 100644
--- a/apps/desktop/src/locales/nb/messages.json
+++ b/apps/desktop/src/locales/nb/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ne/messages.json b/apps/desktop/src/locales/ne/messages.json
index 297708953df..79aa89aed90 100644
--- a/apps/desktop/src/locales/ne/messages.json
+++ b/apps/desktop/src/locales/ne/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/nl/messages.json b/apps/desktop/src/locales/nl/messages.json
index 1cfd5ff5523..29dddbf0f08 100644
--- a/apps/desktop/src/locales/nl/messages.json
+++ b/apps/desktop/src/locales/nl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisatiekluis exporteren"
},
diff --git a/apps/desktop/src/locales/nn/messages.json b/apps/desktop/src/locales/nn/messages.json
index 9195311cc67..753b3ba5524 100644
--- a/apps/desktop/src/locales/nn/messages.json
+++ b/apps/desktop/src/locales/nn/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/or/messages.json b/apps/desktop/src/locales/or/messages.json
index 6f15ed21991..b5c3d4eab46 100644
--- a/apps/desktop/src/locales/or/messages.json
+++ b/apps/desktop/src/locales/or/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/pl/messages.json b/apps/desktop/src/locales/pl/messages.json
index ef8cdc6eb9b..a8b5205a9d9 100644
--- a/apps/desktop/src/locales/pl/messages.json
+++ b/apps/desktop/src/locales/pl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksportowanie sejfu organizacji"
},
diff --git a/apps/desktop/src/locales/pt_BR/messages.json b/apps/desktop/src/locales/pt_BR/messages.json
index e09ee6e16d2..539d254344d 100644
--- a/apps/desktop/src/locales/pt_BR/messages.json
+++ b/apps/desktop/src/locales/pt_BR/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando cofre da organização"
},
diff --git a/apps/desktop/src/locales/pt_PT/messages.json b/apps/desktop/src/locales/pt_PT/messages.json
index 2261357ba08..28ac0c82486 100644
--- a/apps/desktop/src/locales/pt_PT/messages.json
+++ b/apps/desktop/src/locales/pt_PT/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "A exportar o cofre da organização"
},
diff --git a/apps/desktop/src/locales/ro/messages.json b/apps/desktop/src/locales/ro/messages.json
index d148d90a4fd..b160bfc14e3 100644
--- a/apps/desktop/src/locales/ro/messages.json
+++ b/apps/desktop/src/locales/ro/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/ru/messages.json b/apps/desktop/src/locales/ru/messages.json
index ea5b8c6d526..73f52200e04 100644
--- a/apps/desktop/src/locales/ru/messages.json
+++ b/apps/desktop/src/locales/ru/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспорт хранилища организации"
},
diff --git a/apps/desktop/src/locales/si/messages.json b/apps/desktop/src/locales/si/messages.json
index cb837ea4f76..e2f23c7bc56 100644
--- a/apps/desktop/src/locales/si/messages.json
+++ b/apps/desktop/src/locales/si/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/sk/messages.json b/apps/desktop/src/locales/sk/messages.json
index 7eaa1026c00..0f32c9945ce 100644
--- a/apps/desktop/src/locales/sk/messages.json
+++ b/apps/desktop/src/locales/sk/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položky osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportovanie trezora organizácie"
},
diff --git a/apps/desktop/src/locales/sl/messages.json b/apps/desktop/src/locales/sl/messages.json
index 3987587828a..4d50b4d9def 100644
--- a/apps/desktop/src/locales/sl/messages.json
+++ b/apps/desktop/src/locales/sl/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/sr/messages.json b/apps/desktop/src/locales/sr/messages.json
index 69b45469dc8..561e3d35ec2 100644
--- a/apps/desktop/src/locales/sr/messages.json
+++ b/apps/desktop/src/locales/sr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Извоз сефа организације"
},
diff --git a/apps/desktop/src/locales/sv/messages.json b/apps/desktop/src/locales/sv/messages.json
index 1303243a8cc..f33339ea80d 100644
--- a/apps/desktop/src/locales/sv/messages.json
+++ b/apps/desktop/src/locales/sv/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/te/messages.json b/apps/desktop/src/locales/te/messages.json
index f93db44aa69..07404b37fcd 100644
--- a/apps/desktop/src/locales/te/messages.json
+++ b/apps/desktop/src/locales/te/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/th/messages.json b/apps/desktop/src/locales/th/messages.json
index cb9df027ad7..2bec157ec15 100644
--- a/apps/desktop/src/locales/th/messages.json
+++ b/apps/desktop/src/locales/th/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
diff --git a/apps/desktop/src/locales/tr/messages.json b/apps/desktop/src/locales/tr/messages.json
index 55b3b6059b7..974d27f4735 100644
--- a/apps/desktop/src/locales/tr/messages.json
+++ b/apps/desktop/src/locales/tr/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Kuruluş kasasını dışa aktarma"
},
diff --git a/apps/desktop/src/locales/uk/messages.json b/apps/desktop/src/locales/uk/messages.json
index d5f908e011c..6fb5c564f3a 100644
--- a/apps/desktop/src/locales/uk/messages.json
+++ b/apps/desktop/src/locales/uk/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Експортування сховища організації"
},
diff --git a/apps/desktop/src/locales/vi/messages.json b/apps/desktop/src/locales/vi/messages.json
index f6495e4f915..8b5f3250b85 100644
--- a/apps/desktop/src/locales/vi/messages.json
+++ b/apps/desktop/src/locales/vi/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Đang xuất dữ liệu kho tổ chức"
},
diff --git a/apps/desktop/src/locales/zh_CN/messages.json b/apps/desktop/src/locales/zh_CN/messages.json
index 6a12138b48f..e85b2606535 100644
--- a/apps/desktop/src/locales/zh_CN/messages.json
+++ b/apps/desktop/src/locales/zh_CN/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在导出组织密码库"
},
diff --git a/apps/desktop/src/locales/zh_TW/messages.json b/apps/desktop/src/locales/zh_TW/messages.json
index 5da6c4a0b6e..ab09790dcd0 100644
--- a/apps/desktop/src/locales/zh_TW/messages.json
+++ b/apps/desktop/src/locales/zh_TW/messages.json
@@ -2490,6 +2490,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正匯出組織密碼庫"
},
From 251a08fd93c4c5526f1c2d6e398633d70fcca221 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:31:57 +0100
Subject: [PATCH 36/66] Autosync the updated translations (#14037)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/browser/src/_locales/ar/messages.json | 37 ++++
apps/browser/src/_locales/az/messages.json | 37 ++++
apps/browser/src/_locales/be/messages.json | 53 ++++-
apps/browser/src/_locales/bg/messages.json | 37 ++++
apps/browser/src/_locales/bn/messages.json | 37 ++++
apps/browser/src/_locales/bs/messages.json | 37 ++++
apps/browser/src/_locales/ca/messages.json | 37 ++++
apps/browser/src/_locales/cs/messages.json | 37 ++++
apps/browser/src/_locales/cy/messages.json | 37 ++++
apps/browser/src/_locales/da/messages.json | 37 ++++
apps/browser/src/_locales/de/messages.json | 37 ++++
apps/browser/src/_locales/el/messages.json | 37 ++++
apps/browser/src/_locales/en_GB/messages.json | 37 ++++
apps/browser/src/_locales/en_IN/messages.json | 37 ++++
apps/browser/src/_locales/es/messages.json | 37 ++++
apps/browser/src/_locales/et/messages.json | 37 ++++
apps/browser/src/_locales/eu/messages.json | 37 ++++
apps/browser/src/_locales/fa/messages.json | 37 ++++
apps/browser/src/_locales/fi/messages.json | 37 ++++
apps/browser/src/_locales/fil/messages.json | 37 ++++
apps/browser/src/_locales/fr/messages.json | 37 ++++
apps/browser/src/_locales/gl/messages.json | 37 ++++
apps/browser/src/_locales/he/messages.json | 37 ++++
apps/browser/src/_locales/hi/messages.json | 37 ++++
apps/browser/src/_locales/hr/messages.json | 37 ++++
apps/browser/src/_locales/hu/messages.json | 37 ++++
apps/browser/src/_locales/id/messages.json | 193 +++++++++++-------
apps/browser/src/_locales/it/messages.json | 37 ++++
apps/browser/src/_locales/ja/messages.json | 39 +++-
apps/browser/src/_locales/ka/messages.json | 37 ++++
apps/browser/src/_locales/km/messages.json | 37 ++++
apps/browser/src/_locales/kn/messages.json | 37 ++++
apps/browser/src/_locales/ko/messages.json | 37 ++++
apps/browser/src/_locales/lt/messages.json | 37 ++++
apps/browser/src/_locales/lv/messages.json | 37 ++++
apps/browser/src/_locales/ml/messages.json | 37 ++++
apps/browser/src/_locales/mr/messages.json | 37 ++++
apps/browser/src/_locales/my/messages.json | 37 ++++
apps/browser/src/_locales/nb/messages.json | 37 ++++
apps/browser/src/_locales/ne/messages.json | 37 ++++
apps/browser/src/_locales/nl/messages.json | 37 ++++
apps/browser/src/_locales/nn/messages.json | 37 ++++
apps/browser/src/_locales/or/messages.json | 37 ++++
apps/browser/src/_locales/pl/messages.json | 37 ++++
apps/browser/src/_locales/pt_BR/messages.json | 37 ++++
apps/browser/src/_locales/pt_PT/messages.json | 37 ++++
apps/browser/src/_locales/ro/messages.json | 37 ++++
apps/browser/src/_locales/ru/messages.json | 37 ++++
apps/browser/src/_locales/si/messages.json | 37 ++++
apps/browser/src/_locales/sk/messages.json | 37 ++++
apps/browser/src/_locales/sl/messages.json | 37 ++++
apps/browser/src/_locales/sr/messages.json | 37 ++++
apps/browser/src/_locales/sv/messages.json | 37 ++++
apps/browser/src/_locales/te/messages.json | 37 ++++
apps/browser/src/_locales/th/messages.json | 37 ++++
apps/browser/src/_locales/tr/messages.json | 37 ++++
apps/browser/src/_locales/uk/messages.json | 37 ++++
apps/browser/src/_locales/vi/messages.json | 37 ++++
apps/browser/src/_locales/zh_CN/messages.json | 37 ++++
apps/browser/src/_locales/zh_TW/messages.json | 37 ++++
60 files changed, 2307 insertions(+), 87 deletions(-)
diff --git a/apps/browser/src/_locales/ar/messages.json b/apps/browser/src/_locales/ar/messages.json
index 4c051d455ac..bf22a38be08 100644
--- a/apps/browser/src/_locales/ar/messages.json
+++ b/apps/browser/src/_locales/ar/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/az/messages.json b/apps/browser/src/_locales/az/messages.json
index 6f3ace453ae..1ca7642eb19 100644
--- a/apps/browser/src/_locales/az/messages.json
+++ b/apps/browser/src/_locales/az/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Təşkilat seyfini xaricə köçürmə"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Avto-doldur - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopyala: $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/be/messages.json b/apps/browser/src/_locales/be/messages.json
index 905f6b6ac1d..a9a237aeb99 100644
--- a/apps/browser/src/_locales/be/messages.json
+++ b/apps/browser/src/_locales/be/messages.json
@@ -23,7 +23,7 @@
"message": "Упершыню ў Bitwarden?"
},
"logInWithPasskey": {
- "message": "Log in with passkey"
+ "message": "Увайсці з ключом доступу"
},
"useSingleSignOn": {
"message": "Выкарыстаць аднаразовы ўваход"
@@ -35,7 +35,7 @@
"message": "Прызначыць надзейны пароль"
},
"finishCreatingYourAccountBySettingAPassword": {
- "message": "Finish creating your account by setting a password"
+ "message": "Завяршыць стварэнне вашага ўліковага запісу нарадзіўшы пароль"
},
"enterpriseSingleSignOn": {
"message": "Адзіны ўваход прадпрыемства (SSO)"
@@ -62,7 +62,7 @@
"message": "Падказка да асноўнага пароля можа дапамагчы вам успомніць яго, калі вы яго забылі."
},
"masterPassHintText": {
- "message": "If you forget your password, the password hint can be sent to your email. $CURRENT$/$MAXIMUM$ character maximum.",
+ "message": "Падказка пароля можа быць адпраўлена на ваш адрас электроннай пошты, калі вы яго забудзеце. Максімум сімвалаў: $CURRENT$/$MAXIMUM$.",
"placeholders": {
"current": {
"content": "$1",
@@ -81,7 +81,7 @@
"message": "Падказка да асноўнага пароля (неабавязкова)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Ацэнка надзейнасці пароля $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -206,10 +206,10 @@
"message": "Аўтазапаўненне асабістых даных"
},
"fillVerificationCode": {
- "message": "Fill verification code"
+ "message": "Запоўніць праверачны код"
},
"fillVerificationCodeAria": {
- "message": "Fill Verification Code",
+ "message": "Запоўніць праверачны код",
"description": "Aria label for the heading displayed the inline menu for totp code autofill"
},
"generatePasswordCopied": {
@@ -261,7 +261,7 @@
"message": "Запытаць падказку да асноўнага пароля"
},
"enterYourAccountEmailAddressAndYourPasswordHintWillBeSentToYou": {
- "message": "Enter your account email address and your password hint will be sent to you"
+ "message": "Увядзіце адрас электроннай пошты ўліковага запісу і падказка пароля будзе адпраўлена вам"
},
"getMasterPasswordHint": {
"message": "Атрымаць падказку да асноўнага пароля"
@@ -291,7 +291,7 @@
"message": "Працягнуць у вэб-праграме?"
},
"continueToWebAppDesc": {
- "message": "Explore more features of your Bitwarden account on the web app."
+ "message": "Даследуйце больш функцый вашага уліковага запісу Bitwarden у вэб-праграме."
},
"continueToHelpCenter": {
"message": "Працягнуць працу ў Даведачным цэнтры?"
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспартаванне сховішча арганізацыі"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bg/messages.json b/apps/browser/src/_locales/bg/messages.json
index 226e63e32cb..c5dd0237a2c 100644
--- a/apps/browser/src/_locales/bg/messages.json
+++ b/apps/browser/src/_locales/bg/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Изнасяне на трезора на организацията"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Авт. попълване – $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копиране на $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bn/messages.json b/apps/browser/src/_locales/bn/messages.json
index 983b9fadde4..21a455265e4 100644
--- a/apps/browser/src/_locales/bn/messages.json
+++ b/apps/browser/src/_locales/bn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/bs/messages.json b/apps/browser/src/_locales/bs/messages.json
index 08fedb6f10a..a2457c94080 100644
--- a/apps/browser/src/_locales/bs/messages.json
+++ b/apps/browser/src/_locales/bs/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ca/messages.json b/apps/browser/src/_locales/ca/messages.json
index 14e4a577440..d16a679824d 100644
--- a/apps/browser/src/_locales/ca/messages.json
+++ b/apps/browser/src/_locales/ca/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Només s'exportaran els elements personals incloent adjunts de la caixa forta associats a $EMAIL$. Els elements de la caixa forta de l'organització no s'inclouran",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "S'està exportant la caixa forta de l’organització"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/cs/messages.json b/apps/browser/src/_locales/cs/messages.json
index e106d371d57..e6bf4a728e4 100644
--- a/apps/browser/src/_locales/cs/messages.json
+++ b/apps/browser/src/_locales/cs/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportování trezoru organizace"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobrazit položku - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatické vyplnění - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatické vyplnění - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopírovat $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/cy/messages.json b/apps/browser/src/_locales/cy/messages.json
index 83d09d13273..5ccff5a8332 100644
--- a/apps/browser/src/_locales/cy/messages.json
+++ b/apps/browser/src/_locales/cy/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/da/messages.json b/apps/browser/src/_locales/da/messages.json
index 69c8b28d29a..f66f8f34495 100644
--- a/apps/browser/src/_locales/da/messages.json
+++ b/apps/browser/src/_locales/da/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksport af organisationsboks"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoudfyld - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/de/messages.json b/apps/browser/src/_locales/de/messages.json
index a7439db6432..25e8b53cdb5 100644
--- a/apps/browser/src/_locales/de/messages.json
+++ b/apps/browser/src/_locales/de/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Tresor der Organisation wird exportiert"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-Ausfüllen - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ kopieren",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/el/messages.json b/apps/browser/src/_locales/el/messages.json
index fc07f12a48f..47dca3701ec 100644
--- a/apps/browser/src/_locales/el/messages.json
+++ b/apps/browser/src/_locales/el/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Εξαγωγή θησαυ/κίου οργανισμού"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Αυτόματη συμπλήρωση - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/en_GB/messages.json b/apps/browser/src/_locales/en_GB/messages.json
index 1381afbdc6e..9c6d212f8c9 100644
--- a/apps/browser/src/_locales/en_GB/messages.json
+++ b/apps/browser/src/_locales/en_GB/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/en_IN/messages.json b/apps/browser/src/_locales/en_IN/messages.json
index 6b14a358148..aa7a234246f 100644
--- a/apps/browser/src/_locales/en_IN/messages.json
+++ b/apps/browser/src/_locales/en_IN/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organisation vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-fill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/es/messages.json b/apps/browser/src/_locales/es/messages.json
index 4d430d23337..d282011e628 100644
--- a/apps/browser/src/_locales/es/messages.json
+++ b/apps/browser/src/_locales/es/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando caja fuerte de la organización"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autocompletar - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/et/messages.json b/apps/browser/src/_locales/et/messages.json
index 2daf5f9d7f0..e2ad9a1e53a 100644
--- a/apps/browser/src/_locales/et/messages.json
+++ b/apps/browser/src/_locales/et/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/eu/messages.json b/apps/browser/src/_locales/eu/messages.json
index b1e1ca3526b..95d843a3aa8 100644
--- a/apps/browser/src/_locales/eu/messages.json
+++ b/apps/browser/src/_locales/eu/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fa/messages.json b/apps/browser/src/_locales/fa/messages.json
index 2b3d66ad104..0f5616cd001 100644
--- a/apps/browser/src/_locales/fa/messages.json
+++ b/apps/browser/src/_locales/fa/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fi/messages.json b/apps/browser/src/_locales/fi/messages.json
index a37bd0235c1..ff71b93a62d 100644
--- a/apps/browser/src/_locales/fi/messages.json
+++ b/apps/browser/src/_locales/fi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisaation holvin vienti"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automaattitäytä - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopioi $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fil/messages.json b/apps/browser/src/_locales/fil/messages.json
index 9f991843f4f..292c5fd0576 100644
--- a/apps/browser/src/_locales/fil/messages.json
+++ b/apps/browser/src/_locales/fil/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/fr/messages.json b/apps/browser/src/_locales/fr/messages.json
index 32b6dd0296b..5ec06f52a76 100644
--- a/apps/browser/src/_locales/fr/messages.json
+++ b/apps/browser/src/_locales/fr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Export du coffre de l'organisation"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Saisie automatique - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copier $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/gl/messages.json b/apps/browser/src/_locales/gl/messages.json
index 6c0b9cce87b..c76c60114c9 100644
--- a/apps/browser/src/_locales/gl/messages.json
+++ b/apps/browser/src/_locales/gl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportar Caixa forte da organización"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoenchido - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/he/messages.json b/apps/browser/src/_locales/he/messages.json
index 035bf9da48e..0d15c90c3d1 100644
--- a/apps/browser/src/_locales/he/messages.json
+++ b/apps/browser/src/_locales/he/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "מייצא כספת ארגון"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "מילוי אוטומטי - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "העתק $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hi/messages.json b/apps/browser/src/_locales/hi/messages.json
index 748d9eb966b..6dc3dced829 100644
--- a/apps/browser/src/_locales/hi/messages.json
+++ b/apps/browser/src/_locales/hi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "केवल $EMAIL$ से जुड़े अनुलग्नकों सहित व्यक्तिगत वॉल्ट आइटम ही निर्यात किए जाएंगे. संगठन वॉल्ट आइटम शामिल नहीं किए जाएंगे",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hr/messages.json b/apps/browser/src/_locales/hr/messages.json
index b88fc45493f..eda6d7267a3 100644
--- a/apps/browser/src/_locales/hr/messages.json
+++ b/apps/browser/src/_locales/hr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izvoz organizacijskog trezora"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-ispuna - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopiraj $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/hu/messages.json b/apps/browser/src/_locales/hu/messages.json
index 80b1dbf095d..ec313c2cd10 100644
--- a/apps/browser/src/_locales/hu/messages.json
+++ b/apps/browser/src/_locales/hu/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Szervezeti széf exportálása"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatikus kitöltés - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ másolása",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/id/messages.json b/apps/browser/src/_locales/id/messages.json
index 0146fb2a000..daccc3d8272 100644
--- a/apps/browser/src/_locales/id/messages.json
+++ b/apps/browser/src/_locales/id/messages.json
@@ -81,7 +81,7 @@
"message": "Petunjuk Kata Sandi Utama (opsional)"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "Skor kekuatan kata sandi $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -656,10 +656,10 @@
"message": "Verifikasikan identitas Anda"
},
"weDontRecognizeThisDevice": {
- "message": "We don't recognize this device. Enter the code sent to your email to verify your identity."
+ "message": "Kami tidak mengenali perangkat ini. Masukkan kode yang dikirim ke surel Anda untuk memverifikasi identitas Anda."
},
"continueLoggingIn": {
- "message": "Continue logging in"
+ "message": "Lanjutkan log masuk"
},
"yourVaultIsLocked": {
"message": "Brankas Anda terkunci. Verifikasi kata sandi utama Anda untuk melanjutkan."
@@ -869,19 +869,19 @@
"message": "Masuk ke Bitwarden"
},
"enterTheCodeSentToYourEmail": {
- "message": "Enter the code sent to your email"
+ "message": "Masukkan kode yang dikirim ke surel Anda"
},
"enterTheCodeFromYourAuthenticatorApp": {
- "message": "Enter the code from your authenticator app"
+ "message": "Masukkan kode dari aplikasi autentikator Anda"
},
"pressYourYubiKeyToAuthenticate": {
- "message": "Press your YubiKey to authenticate"
+ "message": "Sentuh YubiKey Anda untuk mengautentikasi"
},
"duoTwoFactorRequiredPageSubtitle": {
- "message": "Duo two-step login is required for your account. Follow the steps below to finish logging in."
+ "message": "Log masuk dua langkah berganda diperlukan bagi akun Anda. Ikuti langkah di bawah untuk menyelesaikan log masuk."
},
"followTheStepsBelowToFinishLoggingIn": {
- "message": "Follow the steps below to finish logging in."
+ "message": "Ikuti langkah-langkah di bawah untuk menyelesaikan log masuk."
},
"restartRegistration": {
"message": "Mulai ulang pendaftaran"
@@ -1019,7 +1019,7 @@
"message": "Tanyakan untuk menambah sebuah benda jika benda itu tidak ditemukan di brankas Anda. Diterapkan ke seluruh akun yang telah masuk."
},
"showCardsInVaultViewV2": {
- "message": "Always show cards as Autofill suggestions on Vault view"
+ "message": "Selalu tampilan kartu sebagai saran isi otomatis pada tampilan Brankas"
},
"showCardsCurrentTab": {
"message": "Tamplikan kartu pada halaman Tab"
@@ -1028,7 +1028,7 @@
"message": "Buat tampilan daftar benda dari kartu pada halaman Tab untuk isi otomatis yang mudah."
},
"showIdentitiesInVaultViewV2": {
- "message": "Always show identities as Autofill suggestions on Vault view"
+ "message": "Selalu tampilan identitas sebagai saran isi otomatis pada tampilan Brankas"
},
"showIdentitiesCurrentTab": {
"message": "Tampilkan identitas pada halaman Tab"
@@ -1037,10 +1037,10 @@
"message": "Buat tampilan daftar benda dari identitas pada halaman Tab untuk isi otomatis yang mudah."
},
"clickToAutofillOnVault": {
- "message": "Click items to autofill on Vault view"
+ "message": "Klik butir untuk mengisi otomatis pada tampilan Brankas"
},
"clickToAutofill": {
- "message": "Click items in autofill suggestion to fill"
+ "message": "Klik butir dalam saran isi otomatis untuk mengisi"
},
"clearClipboard": {
"message": "Hapus Papan Klip",
@@ -1057,7 +1057,7 @@
"message": "Iya, Simpan Sekarang"
},
"loginSaveSuccessDetails": {
- "message": "$USERNAME$ saved to Bitwarden.",
+ "message": "$USERNAME$ disimpan ke Bitwarden.",
"placeholders": {
"username": {
"content": "$1"
@@ -1066,7 +1066,7 @@
"description": "Shown to user after login is saved."
},
"loginUpdatedSuccessDetails": {
- "message": "$USERNAME$ updated in Bitwarden.",
+ "message": "$USERNAME$ diperbarui di Bitwarden.",
"placeholders": {
"username": {
"content": "$1"
@@ -1075,35 +1075,35 @@
"description": "Shown to user after login is updated."
},
"saveAsNewLoginAction": {
- "message": "Save as new login",
+ "message": "Simpan sebagai log masuk baru",
"description": "Button text for saving login details as a new entry."
},
"updateLoginAction": {
- "message": "Update login",
+ "message": "Perbarui log masuk",
"description": "Button text for updating an existing login entry."
},
"saveLoginPrompt": {
- "message": "Save login?",
+ "message": "Simpan log masuk?",
"description": "Prompt asking the user if they want to save their login details."
},
"updateLoginPrompt": {
- "message": "Update existing login?",
+ "message": "Perbarui log masuk yang ada?",
"description": "Prompt asking the user if they want to update an existing login entry."
},
"loginSaveSuccess": {
- "message": "Login saved",
+ "message": "Log masuk disimpan",
"description": "Message displayed when login details are successfully saved."
},
"loginUpdateSuccess": {
- "message": "Login updated",
+ "message": "Log masuk diperbarui",
"description": "Message displayed when login details are successfully updated."
},
"saveFailure": {
- "message": "Error saving",
+ "message": "Kesalahan saat menyimpan",
"description": "Error message shown when the system fails to save login details."
},
"saveFailureDetails": {
- "message": "Oh no! We couldn't save this. Try entering the details manually.",
+ "message": "Oh tidak! Kami tidak bisa menyimpan ini. Cobalah memasukkan rincian secara manual.",
"description": "Detailed error message shown when saving login details fails."
},
"enableChangedPasswordNotification": {
@@ -1422,7 +1422,7 @@
"message": "Ingat saya"
},
"dontAskAgainOnThisDeviceFor30Days": {
- "message": "Don't ask again on this device for 30 days"
+ "message": "Jangan tanyakan lagi pada perangkat ini untuk 30 hari"
},
"sendVerificationCodeEmailAgain": {
"message": "Kirim ulang email kode verifikasi"
@@ -1431,11 +1431,11 @@
"message": "Gunakan metode masuk dua langkah lainnya"
},
"selectAnotherMethod": {
- "message": "Select another method",
+ "message": "Pilih metode lain",
"description": "Select another two-step login method"
},
"useYourRecoveryCode": {
- "message": "Use your recovery code"
+ "message": "Gunakan kode pemulihan Anda"
},
"insertYubiKey": {
"message": "Masukkan YubiKey Anda ke port USB komputer Anda, lalu sentuh tombolnya."
@@ -1450,16 +1450,16 @@
"message": "Buka tab baru"
},
"openInNewTab": {
- "message": "Open in new tab"
+ "message": "Buka dalam tab baru"
},
"webAuthnAuthenticate": {
"message": "Autentikasi dengan WebAuthn."
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "Baca kunci keamanan"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "Menunggu interaksi kunci keamanan..."
},
"loginUnavailable": {
"message": "Info Masuk Tidak Tersedia"
@@ -1474,7 +1474,7 @@
"message": "Opsi Info Masuk Dua Langkah"
},
"selectTwoStepLoginMethod": {
- "message": "Select two-step login method"
+ "message": "Pilih metode log masuk dua langkah"
},
"recoveryCodeDesc": {
"message": "Kehilangan akses ke semua penyedia dua faktor Anda? Gunakan kode pemulihan untuk menonaktifkan semua penyedia dua faktor dari akun Anda."
@@ -1668,7 +1668,7 @@
"message": "Seret untuk mengurutkan"
},
"dragToReorder": {
- "message": "Drag to reorder"
+ "message": "Seret untuk mengubah urutan"
},
"cfTypeText": {
"message": "Teks"
@@ -2144,7 +2144,7 @@
"message": "Pembuat nama pengguna"
},
"useThisEmail": {
- "message": "Use this email"
+ "message": "Pakai surel ini"
},
"useThisPassword": {
"message": "Gunakan kata sandi ini"
@@ -2164,7 +2164,7 @@
"description": "This will be used as part of a larger sentence, broken up to include the generator icon. The full sentence will read 'Use the generator [GENERATOR_ICON] to create a strong unique password'"
},
"vaultCustomization": {
- "message": "Vault customization"
+ "message": "Penyesuaian brankas"
},
"vaultTimeoutAction": {
"message": "Tindakan Batas Waktu Brankas"
@@ -2173,13 +2173,13 @@
"message": "Batas waktu tindakan"
},
"newCustomizationOptionsCalloutTitle": {
- "message": "New customization options"
+ "message": "Opsi penyesuaian baru"
},
"newCustomizationOptionsCalloutContent": {
- "message": "Customize your vault experience with quick copy actions, compact mode, and more!"
+ "message": "Sesuaikan pengalaman brankas Anda dengan aksi salin cepat, mode kompak, dan lainnya!"
},
"newCustomizationOptionsCalloutLink": {
- "message": "View all Appearance settings"
+ "message": "Lihat semua pengaturan Penampilan"
},
"lock": {
"message": "Kunci",
@@ -2437,10 +2437,10 @@
"description": "A category title describing the concept of web domains"
},
"blockedDomains": {
- "message": "Blocked domains"
+ "message": "Domain terblokir"
},
"learnMoreAboutBlockedDomains": {
- "message": "Learn more about blocked domains"
+ "message": "Pelajari lebih lanjut tentang domain yang diblokir"
},
"excludedDomains": {
"message": "Domain yang Dikecualikan"
@@ -2452,19 +2452,19 @@
"message": "Bitwarden tidak akan meminta untuk menyimpan rincian login untuk domain tersebut. Anda harus menyegarkan halaman agar perubahan diterapkan."
},
"blockedDomainsDesc": {
- "message": "Autofill and other related features will not be offered for these websites. You must refresh the page for changes to take effect."
+ "message": "Isi otomatis dan fitur terkait lain tidak akan ditawarkan bagi situs-situs web ini. Anda mesti menyegarkan halaman agar perubahan berdampak."
},
"autofillBlockedNoticeV2": {
- "message": "Autofill is blocked for this website."
+ "message": "Isi otomatis diblokir bagi situs web ini."
},
"autofillBlockedNoticeGuidance": {
- "message": "Change this in settings"
+ "message": "Ubah ini di pengaturan"
},
"change": {
"message": "Ubah"
},
"changeButtonTitle": {
- "message": "Change password - $ITEMNAME$",
+ "message": "Ubah kata sandi - $ITEMNAME$",
"placeholders": {
"itemname": {
"content": "$1",
@@ -2473,10 +2473,10 @@
}
},
"atRiskPasswords": {
- "message": "At-risk passwords"
+ "message": "Kata sandi yang berrisiko"
},
"atRiskPasswordDescSingleOrg": {
- "message": "$ORGANIZATION$ is requesting you change one password because it is at-risk.",
+ "message": "$ORGANIZATION$ meminta Ada mengubah satu kata sandi karena itu berrisiko.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2485,7 +2485,7 @@
}
},
"atRiskPasswordsDescSingleOrgPlural": {
- "message": "$ORGANIZATION$ is requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "$ORGANIZATION$ meminta Anda mengubah $COUNT$ kata sandi karena mereka berrisiko.",
"placeholders": {
"organization": {
"content": "$1",
@@ -2498,7 +2498,7 @@
}
},
"atRiskPasswordsDescMultiOrgPlural": {
- "message": "Your organizations are requesting you change the $COUNT$ passwords because they are at-risk.",
+ "message": "Organisasi Anda meminta Anda mengubah $COUNT$ kata sandi karena mereka berrisiko.",
"placeholders": {
"count": {
"content": "$1",
@@ -2507,10 +2507,10 @@
}
},
"reviewAndChangeAtRiskPassword": {
- "message": "Review and change one at-risk password"
+ "message": "Tinjau dan ubah satu kata sandi berrisiko"
},
"reviewAndChangeAtRiskPasswordsPlural": {
- "message": "Review and change $COUNT$ at-risk passwords",
+ "message": "Tinjau dan ubah $COUNT$ kata sandi berrisiko",
"placeholders": {
"count": {
"content": "$1",
@@ -2519,7 +2519,7 @@
}
},
"changeAtRiskPasswordsFaster": {
- "message": "Change at-risk passwords faster"
+ "message": "Ubah lebih cepat kata sandi yang berrisiko"
},
"changeAtRiskPasswordsFasterDesc": {
"message": "Update your settings so you can quickly autofill your passwords and generate new ones"
@@ -2528,14 +2528,14 @@
"message": "Review at-risk logins"
},
"reviewAtRiskPasswords": {
- "message": "Review at-risk passwords"
+ "message": "Tinjau kata sandi yang berrisiko"
},
"reviewAtRiskLoginsSlideDesc": {
- "message": "Your organization passwords are at-risk because they are weak, reused, and/or exposed.",
+ "message": "Kata sandi organisasi Anda berrisiko karena mereka lemah, dipakai ulang, dan/atau terpapar.",
"description": "Description of the review at-risk login slide on the at-risk password page carousel"
},
"reviewAtRiskLoginSlideImgAlt": {
- "message": "Illustration of a list of logins that are at-risk"
+ "message": "Ilustrasi dari daftar log masuk yang berrisiko"
},
"generatePasswordSlideDesc": {
"message": "Quickly generate a strong, unique password with the Bitwarden autofill menu on the at-risk site.",
@@ -2561,7 +2561,7 @@
"message": "Turned on autofill"
},
"dismiss": {
- "message": "Dismiss"
+ "message": "Tutup"
},
"websiteItemLabel": {
"message": "Situs web $number$ (URI)",
@@ -2582,7 +2582,7 @@
}
},
"blockedDomainsSavedSuccess": {
- "message": "Blocked domain changes saved"
+ "message": "Perubahan domain yang diblokir disimpan"
},
"excludedDomainsSavedSuccess": {
"message": "Perubahan domain yang diabaikan telah disimpan"
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Mengekspor brankas organisasi"
},
@@ -3023,17 +3032,17 @@
"message": "Galat"
},
"decryptionError": {
- "message": "Decryption error"
+ "message": "Kesalahan dekripsi"
},
"couldNotDecryptVaultItemsBelow": {
- "message": "Bitwarden could not decrypt the vault item(s) listed below."
+ "message": "Bitwarden tidak bisa mendekripsi butir brankas yang tercantum di bawah."
},
"contactCSToAvoidDataLossPart1": {
"message": "Contact customer success",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"contactCSToAvoidDataLossPart2": {
- "message": "to avoid additional data loss.",
+ "message": "untuk menghindari lanjutan hilang data.",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"generateUsername": {
@@ -3168,7 +3177,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ menolak permintaan Anda. Harap hubungi penyedia layanan Anda untuk bantuan.",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -3178,7 +3187,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ menolak permintaan Anda: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -3327,13 +3336,13 @@
"message": "Sebuah pemberitahuan dikirim ke perangkat Anda."
},
"notificationSentDevicePart1": {
- "message": "Unlock Bitwarden on your device or on the"
+ "message": "Buka kunci Bitwarden pada perangkat Anda atau pada"
},
"notificationSentDeviceAnchor": {
- "message": "web app"
+ "message": "aplikasi web"
},
"notificationSentDevicePart2": {
- "message": "Make sure the Fingerprint phrase matches the one below before approving."
+ "message": "Pastikan frasa Sidik Jari cocok dengan yang di bawah sebelum menyetujui."
},
"aNotificationWasSentToYourDevice": {
"message": "Sebuah pemberitahuan telah dikirim ke perangkat Anda"
@@ -3348,7 +3357,7 @@
"message": "Memulai login"
},
"logInRequestSent": {
- "message": "Request sent"
+ "message": "Permintaan terkirim"
},
"exposedMasterPassword": {
"message": "Kata Sandi Utama yang Terpapar"
@@ -4080,7 +4089,7 @@
"message": "Akun aktif"
},
"bitwardenAccount": {
- "message": "Bitwarden account"
+ "message": "Akun Bitwarden"
},
"availableAccounts": {
"message": "Akun yang tersedia"
@@ -4203,10 +4212,10 @@
"message": "Kunci sandi dihapus"
},
"autofillSuggestions": {
- "message": "Autofill suggestions"
+ "message": "Saran isi otomatis"
},
"itemSuggestions": {
- "message": "Suggested items"
+ "message": "Butir yang disarankan"
},
"autofillSuggestionsTip": {
"message": "Simpan benda login untuk situs ini ke isi otomatis"
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Isi otomatis - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,8 +4303,22 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
- "message": "Copy $FIELD$, $VALUE$",
+ "message": "Salin $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
"placeholders": {
"field": {
@@ -4769,7 +4806,7 @@
}
},
"itemLocation": {
- "message": "Item Location"
+ "message": "Lokasi Item"
},
"fileSend": {
"message": "File Send"
@@ -4835,7 +4872,7 @@
"message": "File saved to device. Manage from your device downloads."
},
"showCharacterCount": {
- "message": "Show character count"
+ "message": "Tunjukkan cacah karakter"
},
"hideCharacterCount": {
"message": "Hide character count"
@@ -4896,15 +4933,15 @@
"description": "Heading for the password generator within the inline menu"
},
"passwordRegenerated": {
- "message": "Password regenerated",
+ "message": "Kata sandi dibuat ulang",
"description": "Notification message for when a password has been regenerated"
},
"saveLoginToBitwarden": {
- "message": "Save login to Bitwarden?",
+ "message": "Simpan log masuk ke Bitwarden?",
"description": "Confirmation message for saving a login to Bitwarden"
},
"spaceCharacterDescriptor": {
- "message": "Space",
+ "message": "Spasi",
"description": "Represents the space key in screen reader content as a readable word"
},
"tildeCharacterDescriptor": {
@@ -5051,22 +5088,22 @@
"message": "Beta"
},
"importantNotice": {
- "message": "Important notice"
+ "message": "Pemberitahuan penting"
},
"setupTwoStepLogin": {
- "message": "Set up two-step login"
+ "message": "Siapkan log masuk dua langkah"
},
"newDeviceVerificationNoticeContentPage1": {
- "message": "Bitwarden will send a code to your account email to verify logins from new devices starting in February 2025."
+ "message": "Bitwarden akan mengirim suatu kode ke akun surel Anda untuk memverifikasi log masuk dari perangkat baru sejak Februari 2025."
},
"newDeviceVerificationNoticeContentPage2": {
- "message": "You can set up two-step login as an alternative way to protect your account or change your email to one you can access."
+ "message": "Anda dapat menyiapkan log masuk dua langkah sebagai cara alternatif untuk melindungi akun Anda atau mengubah surel Anda ke yang bisa Anda akses."
},
"remindMeLater": {
- "message": "Remind me later"
+ "message": "Ingatkan saya nanti"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "Do you have reliable access to your email, $EMAIL$?",
+ "message": "Apakah Anda punya akses yang handal ke surel Anda, $EMAIL$?",
"placeholders": {
"email": {
"content": "$1",
@@ -5081,7 +5118,7 @@
"message": "Ya, saya dapat mengakses surel saya secara handla"
},
"turnOnTwoStepLogin": {
- "message": "Turn on two-step login"
+ "message": "Nyalakan log masuk dua langkah"
},
"changeAcctEmail": {
"message": "Ubah surel akun"
diff --git a/apps/browser/src/_locales/it/messages.json b/apps/browser/src/_locales/it/messages.json
index cb8e414ca37..b18acbc79b8 100644
--- a/apps/browser/src/_locales/it/messages.json
+++ b/apps/browser/src/_locales/it/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Esportando cassaforte dell'organizzazione"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Riempi automaticamente - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copia $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ja/messages.json b/apps/browser/src/_locales/ja/messages.json
index b67d0b62a6e..a6dd0f709a2 100644
--- a/apps/browser/src/_locales/ja/messages.json
+++ b/apps/browser/src/_locales/ja/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "組織保管庫のエクスポート"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自動入力 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$ 「$VALUE$」 をコピー",
"description": "Title for a button that copies a field value to the clipboard.",
@@ -5066,7 +5103,7 @@
"message": "後で再通知"
},
"newDeviceVerificationNoticePageOneFormContent": {
- "message": "新しいメールアドレス $EMAIL$ はあなたが管理しているものですか?",
+ "message": "メールアドレス $EMAIL$ は、確実にアクセスできるものですか?",
"placeholders": {
"email": {
"content": "$1",
diff --git a/apps/browser/src/_locales/ka/messages.json b/apps/browser/src/_locales/ka/messages.json
index 818d7cdcd19..0e11594faac 100644
--- a/apps/browser/src/_locales/ka/messages.json
+++ b/apps/browser/src/_locales/ka/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/km/messages.json b/apps/browser/src/_locales/km/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/km/messages.json
+++ b/apps/browser/src/_locales/km/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/kn/messages.json b/apps/browser/src/_locales/kn/messages.json
index c8aff3a6488..31ea7daa668 100644
--- a/apps/browser/src/_locales/kn/messages.json
+++ b/apps/browser/src/_locales/kn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ko/messages.json b/apps/browser/src/_locales/ko/messages.json
index cd54ac47506..92654f84e31 100644
--- a/apps/browser/src/_locales/ko/messages.json
+++ b/apps/browser/src/_locales/ko/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "조직 보관함을 내보내는 중"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "자동 완성 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/lt/messages.json b/apps/browser/src/_locales/lt/messages.json
index 8bf8a8f7518..e1536abdf83 100644
--- a/apps/browser/src/_locales/lt/messages.json
+++ b/apps/browser/src/_locales/lt/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/lv/messages.json b/apps/browser/src/_locales/lv/messages.json
index 7b5f077f69d..789666ef2c1 100644
--- a/apps/browser/src/_locales/lv/messages.json
+++ b/apps/browser/src/_locales/lv/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Izgūst apvienības glabātavu"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Apskatīt vienumu - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automātiski aizpildīt - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automātiskā aizpilde - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Ievietot starpliktuvē $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ml/messages.json b/apps/browser/src/_locales/ml/messages.json
index 24a096db0ef..11af6b54202 100644
--- a/apps/browser/src/_locales/ml/messages.json
+++ b/apps/browser/src/_locales/ml/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/mr/messages.json b/apps/browser/src/_locales/mr/messages.json
index 9a49998d3d9..472a8378bb7 100644
--- a/apps/browser/src/_locales/mr/messages.json
+++ b/apps/browser/src/_locales/mr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/my/messages.json b/apps/browser/src/_locales/my/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/my/messages.json
+++ b/apps/browser/src/_locales/my/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nb/messages.json b/apps/browser/src/_locales/nb/messages.json
index 0ff4ed9486a..9a52eeea4cb 100644
--- a/apps/browser/src/_locales/nb/messages.json
+++ b/apps/browser/src/_locales/nb/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autoutfyll - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ne/messages.json b/apps/browser/src/_locales/ne/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/ne/messages.json
+++ b/apps/browser/src/_locales/ne/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nl/messages.json b/apps/browser/src/_locales/nl/messages.json
index 1c2b09ca3e3..80877839adb 100644
--- a/apps/browser/src/_locales/nl/messages.json
+++ b/apps/browser/src/_locales/nl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Organisatiekluis exporteren"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Item weergeven - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatisch invullen - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatisch aanvullen - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "$FIELD$, $VALUE$ kopiëren",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/nn/messages.json b/apps/browser/src/_locales/nn/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/nn/messages.json
+++ b/apps/browser/src/_locales/nn/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/or/messages.json b/apps/browser/src/_locales/or/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/or/messages.json
+++ b/apps/browser/src/_locales/or/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pl/messages.json b/apps/browser/src/_locales/pl/messages.json
index f4c1303bd18..c345972bd4a 100644
--- a/apps/browser/src/_locales/pl/messages.json
+++ b/apps/browser/src/_locales/pl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tylko poszczególne elementy sejfu łącznie z załącznikami powiązanymi z $EMAIL$ zostaną wyeksportowane. Elementy sejfu organizacji nie będą dołączone",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Eksportowanie sejfu organizacji"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobacz element - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autouzupełnij - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autouzupełnij - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopiuj $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pt_BR/messages.json b/apps/browser/src/_locales/pt_BR/messages.json
index 0b5d569f443..5aacad23a93 100644
--- a/apps/browser/src/_locales/pt_BR/messages.json
+++ b/apps/browser/src/_locales/pt_BR/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportando cofre da organização"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Auto-preenchimento - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copiar $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/pt_PT/messages.json b/apps/browser/src/_locales/pt_PT/messages.json
index c1f1f51609c..0e467b65183 100644
--- a/apps/browser/src/_locales/pt_PT/messages.json
+++ b/apps/browser/src/_locales/pt_PT/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "A exportar o cofre da organização"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Ver item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Preencher automaticamente - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Preencher automaticamente - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copiar $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ro/messages.json b/apps/browser/src/_locales/ro/messages.json
index 5ce73eb2e49..fa4d754e99d 100644
--- a/apps/browser/src/_locales/ro/messages.json
+++ b/apps/browser/src/_locales/ro/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/ru/messages.json b/apps/browser/src/_locales/ru/messages.json
index 11d8de4acbe..c775bee3289 100644
--- a/apps/browser/src/_locales/ru/messages.json
+++ b/apps/browser/src/_locales/ru/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Экспорт хранилища организации"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Автозаполнение - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Скопировать $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/si/messages.json b/apps/browser/src/_locales/si/messages.json
index 721d16a2eee..12bac21e11f 100644
--- a/apps/browser/src/_locales/si/messages.json
+++ b/apps/browser/src/_locales/si/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sk/messages.json b/apps/browser/src/_locales/sk/messages.json
index 897db9f3176..cd4095f8ff5 100644
--- a/apps/browser/src/_locales/sk/messages.json
+++ b/apps/browser/src/_locales/sk/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položky osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exportovanie trezora organizácie"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Zobraziť položku - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Automatické vyplnenie - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Automatické vyplnenie - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopírovať $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sl/messages.json b/apps/browser/src/_locales/sl/messages.json
index f6a543ea5ea..c77d4cb55f4 100644
--- a/apps/browser/src/_locales/sl/messages.json
+++ b/apps/browser/src/_locales/sl/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sr/messages.json b/apps/browser/src/_locales/sr/messages.json
index 3c7bef94c13..5e325c6a97c 100644
--- a/apps/browser/src/_locales/sr/messages.json
+++ b/apps/browser/src/_locales/sr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Извоз сефа организације"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Ауто-пуњење - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копирај $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/sv/messages.json b/apps/browser/src/_locales/sv/messages.json
index 6db7a22490e..e3ff6986cae 100644
--- a/apps/browser/src/_locales/sv/messages.json
+++ b/apps/browser/src/_locales/sv/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/te/messages.json b/apps/browser/src/_locales/te/messages.json
index c9c29611deb..0c087ef7de9 100644
--- a/apps/browser/src/_locales/te/messages.json
+++ b/apps/browser/src/_locales/te/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/th/messages.json b/apps/browser/src/_locales/th/messages.json
index c545f802d64..e2225327664 100644
--- a/apps/browser/src/_locales/th/messages.json
+++ b/apps/browser/src/_locales/th/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Exporting organization vault"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Autofill - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/tr/messages.json b/apps/browser/src/_locales/tr/messages.json
index e69b33d63af..e8788a00d70 100644
--- a/apps/browser/src/_locales/tr/messages.json
+++ b/apps/browser/src/_locales/tr/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Kuruluş kasasını dışa aktarma"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "Kaydı görüntüle - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Otomatik doldur - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Otomatik doldur - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Kopyala: $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/uk/messages.json b/apps/browser/src/_locales/uk/messages.json
index d18d90babff..6bd9ca7fc36 100644
--- a/apps/browser/src/_locales/uk/messages.json
+++ b/apps/browser/src/_locales/uk/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Експортування сховища організації"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Автозаповнення – $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Копіювати $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/vi/messages.json b/apps/browser/src/_locales/vi/messages.json
index db0da3b5874..35c553f72f1 100644
--- a/apps/browser/src/_locales/vi/messages.json
+++ b/apps/browser/src/_locales/vi/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "Đang xuất dữ liệu kho tổ chức"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "Tự động điền - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/zh_CN/messages.json b/apps/browser/src/_locales/zh_CN/messages.json
index bdfaeb92531..fc6f70cb6a4 100644
--- a/apps/browser/src/_locales/zh_CN/messages.json
+++ b/apps/browser/src/_locales/zh_CN/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在导出组织密码库"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "查看项目 - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自动填充 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "自动填充 - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "复制 $FIELD$,$VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
diff --git a/apps/browser/src/_locales/zh_TW/messages.json b/apps/browser/src/_locales/zh_TW/messages.json
index 37e450c8fd2..365aae61ebf 100644
--- a/apps/browser/src/_locales/zh_TW/messages.json
+++ b/apps/browser/src/_locales/zh_TW/messages.json
@@ -3007,6 +3007,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultTitle": {
"message": "正在匯出組織密碼庫"
},
@@ -4270,6 +4279,20 @@
}
}
},
+ "viewItemTitleWithField": {
+ "message": "View item - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a link that opens a view for an item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"autofillTitle": {
"message": "自動填入 - $ITEMNAME$",
"description": "Title for a button that autofills a login item.",
@@ -4280,6 +4303,20 @@
}
}
},
+ "autofillTitleWithField": {
+ "message": "Autofill - $ITEMNAME$ - $FIELD$",
+ "description": "Title for a button that autofills a login item.",
+ "placeholders": {
+ "itemname": {
+ "content": "$1",
+ "example": "Secret Item"
+ },
+ "field": {
+ "content": "$2",
+ "example": "Username"
+ }
+ }
+ },
"copyFieldValue": {
"message": "Copy $FIELD$, $VALUE$",
"description": "Title for a button that copies a field value to the clipboard.",
From 8b14b0c09f0df761ba2ea28dd2c35a4f8d2e0f96 Mon Sep 17 00:00:00 2001
From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:34:52 +0100
Subject: [PATCH 37/66] Autosync the updated translations (#14039)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
---
apps/web/src/locales/af/messages.json | 9 ++
apps/web/src/locales/ar/messages.json | 9 ++
apps/web/src/locales/az/messages.json | 9 ++
apps/web/src/locales/be/messages.json | 9 ++
apps/web/src/locales/bg/messages.json | 9 ++
apps/web/src/locales/bn/messages.json | 9 ++
apps/web/src/locales/bs/messages.json | 9 ++
apps/web/src/locales/ca/messages.json | 9 ++
apps/web/src/locales/cs/messages.json | 9 ++
apps/web/src/locales/cy/messages.json | 9 ++
apps/web/src/locales/da/messages.json | 9 ++
apps/web/src/locales/de/messages.json | 9 ++
apps/web/src/locales/el/messages.json | 9 ++
apps/web/src/locales/en_GB/messages.json | 9 ++
apps/web/src/locales/en_IN/messages.json | 9 ++
apps/web/src/locales/eo/messages.json | 9 ++
apps/web/src/locales/es/messages.json | 9 ++
apps/web/src/locales/et/messages.json | 9 ++
apps/web/src/locales/eu/messages.json | 9 ++
apps/web/src/locales/fa/messages.json | 9 ++
apps/web/src/locales/fi/messages.json | 9 ++
apps/web/src/locales/fil/messages.json | 9 ++
apps/web/src/locales/fr/messages.json | 9 ++
apps/web/src/locales/gl/messages.json | 9 ++
apps/web/src/locales/he/messages.json | 9 ++
apps/web/src/locales/hi/messages.json | 9 ++
apps/web/src/locales/hr/messages.json | 9 ++
apps/web/src/locales/hu/messages.json | 9 ++
apps/web/src/locales/id/messages.json | 9 ++
apps/web/src/locales/it/messages.json | 9 ++
apps/web/src/locales/ja/messages.json | 123 ++++++++++++-----------
apps/web/src/locales/ka/messages.json | 9 ++
apps/web/src/locales/km/messages.json | 9 ++
apps/web/src/locales/kn/messages.json | 9 ++
apps/web/src/locales/ko/messages.json | 9 ++
apps/web/src/locales/lv/messages.json | 9 ++
apps/web/src/locales/ml/messages.json | 9 ++
apps/web/src/locales/mr/messages.json | 9 ++
apps/web/src/locales/my/messages.json | 9 ++
apps/web/src/locales/nb/messages.json | 9 ++
apps/web/src/locales/ne/messages.json | 9 ++
apps/web/src/locales/nl/messages.json | 9 ++
apps/web/src/locales/nn/messages.json | 9 ++
apps/web/src/locales/or/messages.json | 9 ++
apps/web/src/locales/pl/messages.json | 9 ++
apps/web/src/locales/pt_BR/messages.json | 9 ++
apps/web/src/locales/pt_PT/messages.json | 9 ++
apps/web/src/locales/ro/messages.json | 9 ++
apps/web/src/locales/ru/messages.json | 9 ++
apps/web/src/locales/si/messages.json | 9 ++
apps/web/src/locales/sk/messages.json | 9 ++
apps/web/src/locales/sl/messages.json | 9 ++
apps/web/src/locales/sr/messages.json | 9 ++
apps/web/src/locales/sr_CS/messages.json | 9 ++
apps/web/src/locales/sv/messages.json | 9 ++
apps/web/src/locales/te/messages.json | 9 ++
apps/web/src/locales/th/messages.json | 9 ++
apps/web/src/locales/tr/messages.json | 9 ++
apps/web/src/locales/uk/messages.json | 9 ++
apps/web/src/locales/vi/messages.json | 9 ++
apps/web/src/locales/zh_CN/messages.json | 13 ++-
apps/web/src/locales/zh_TW/messages.json | 9 ++
62 files changed, 617 insertions(+), 59 deletions(-)
diff --git a/apps/web/src/locales/af/messages.json b/apps/web/src/locales/af/messages.json
index 3b9168b29b2..fa25494f4bf 100644
--- a/apps/web/src/locales/af/messages.json
+++ b/apps/web/src/locales/af/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ar/messages.json b/apps/web/src/locales/ar/messages.json
index 13777f942de..4a289082039 100644
--- a/apps/web/src/locales/ar/messages.json
+++ b/apps/web/src/locales/ar/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/az/messages.json b/apps/web/src/locales/az/messages.json
index f092620b00a..dfafeab1877 100644
--- a/apps/web/src/locales/az/messages.json
+++ b/apps/web/src/locales/az/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnız $EMAIL$ ilə əlaqələndirilmiş qoşmalar daxil olmaqla fərdi anbar elementləri xaricə köçürüləcək. Təşkilat anbar elementləri daxil edilməyəcək",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Yalnız $ORGANIZATION$ ilə əlaqələndirilmiş təşkilat seyfi xaricə köçürüləcək. Fərdi seyfdə və digər təşkilatlardakı elementlər daxil edilməyəcək.",
"placeholders": {
diff --git a/apps/web/src/locales/be/messages.json b/apps/web/src/locales/be/messages.json
index 2e5aeabff32..a22b463f4f2 100644
--- a/apps/web/src/locales/be/messages.json
+++ b/apps/web/src/locales/be/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Толькі сховішча арганізацыі, якія звязаны з $ORGANIZATION$ будуць экспартаваны. Элементы асабістага сховішча і элементы з іншых арганізацый не будуць уключаны.",
"placeholders": {
diff --git a/apps/web/src/locales/bg/messages.json b/apps/web/src/locales/bg/messages.json
index 04d4e25bb7c..0599d3a73f2 100644
--- a/apps/web/src/locales/bg/messages.json
+++ b/apps/web/src/locales/bg/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Ще бъдат изнесени само записите и прикачените файлове от личния трезор свързан с $EMAIL$. Записите в трезора на организацията няма да бъдат включени.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Ще бъдат изнесени само записите от трезора свързан с $ORGANIZATION$. Записите в отделните лични трезори и тези в други организации няма да бъдат включени.",
"placeholders": {
diff --git a/apps/web/src/locales/bn/messages.json b/apps/web/src/locales/bn/messages.json
index 8c082c57e30..15ca72e1642 100644
--- a/apps/web/src/locales/bn/messages.json
+++ b/apps/web/src/locales/bn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/bs/messages.json b/apps/web/src/locales/bs/messages.json
index 7efb47bd86c..2c0f177ad67 100644
--- a/apps/web/src/locales/bs/messages.json
+++ b/apps/web/src/locales/bs/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ca/messages.json b/apps/web/src/locales/ca/messages.json
index a4f2aeb7f55..d394a75b70f 100644
--- a/apps/web/src/locales/ca/messages.json
+++ b/apps/web/src/locales/ca/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Només s'exportarà la caixa forta de l'organització associada a $ORGANIZATION$. No s'inclouran els elements de les caixes fortes individuals ni d'altres organitzacions.",
"placeholders": {
diff --git a/apps/web/src/locales/cs/messages.json b/apps/web/src/locales/cs/messages.json
index 46b510a6459..552a717ab82 100644
--- a/apps/web/src/locales/cs/messages.json
+++ b/apps/web/src/locales/cs/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Budou exportovány jen osobní položky trezoru včetně příloh spojené s účtem $EMAIL$. Nebudou zahrnuty položky trezoru v organizaci.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exportován bude jen trezor organizace přidružený k položce $ORGANIZATION$. Osobní položky trezoru a položky z jiných organizací nebudou zahrnuty.",
"placeholders": {
diff --git a/apps/web/src/locales/cy/messages.json b/apps/web/src/locales/cy/messages.json
index 2fd61e01526..ba8bd6bd8a4 100644
--- a/apps/web/src/locales/cy/messages.json
+++ b/apps/web/src/locales/cy/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/da/messages.json b/apps/web/src/locales/da/messages.json
index f8a335011e5..dddef79ddd0 100644
--- a/apps/web/src/locales/da/messages.json
+++ b/apps/web/src/locales/da/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Kun organisationsboksen tilknyttet $ORGANIZATION$ eksporteres. Emner i individuelle bokse eller andre organisationer medtages ikke.",
"placeholders": {
diff --git a/apps/web/src/locales/de/messages.json b/apps/web/src/locales/de/messages.json
index 135ab8af923..b5eeb9db7ce 100644
--- a/apps/web/src/locales/de/messages.json
+++ b/apps/web/src/locales/de/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Nur der mit $ORGANIZATION$ verbundene Organisationstresor wird exportiert. Einträge in persönlichen Tresoren oder anderen Organisationen werden nicht berücksichtigt.",
"placeholders": {
diff --git a/apps/web/src/locales/el/messages.json b/apps/web/src/locales/el/messages.json
index e20b5cc52a0..27e39f9798e 100644
--- a/apps/web/src/locales/el/messages.json
+++ b/apps/web/src/locales/el/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/en_GB/messages.json b/apps/web/src/locales/en_GB/messages.json
index b211ec070ab..b4f0ace607a 100644
--- a/apps/web/src/locales/en_GB/messages.json
+++ b/apps/web/src/locales/en_GB/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/en_IN/messages.json b/apps/web/src/locales/en_IN/messages.json
index 6b4b6e11ab3..a8c4bfca62e 100644
--- a/apps/web/src/locales/en_IN/messages.json
+++ b/apps/web/src/locales/en_IN/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organisation vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organisation vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organisations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/eo/messages.json b/apps/web/src/locales/eo/messages.json
index ce40767f4c1..c31274e6198 100644
--- a/apps/web/src/locales/eo/messages.json
+++ b/apps/web/src/locales/eo/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/es/messages.json b/apps/web/src/locales/es/messages.json
index 1c71a29f48b..80b982bd928 100644
--- a/apps/web/src/locales/es/messages.json
+++ b/apps/web/src/locales/es/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/et/messages.json b/apps/web/src/locales/et/messages.json
index f9a5b810ded..a55fce63349 100644
--- a/apps/web/src/locales/et/messages.json
+++ b/apps/web/src/locales/et/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/eu/messages.json b/apps/web/src/locales/eu/messages.json
index d0e99c73e66..a8b64594065 100644
--- a/apps/web/src/locales/eu/messages.json
+++ b/apps/web/src/locales/eu/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/fa/messages.json b/apps/web/src/locales/fa/messages.json
index 04cddc65f2f..4657d4a9732 100644
--- a/apps/web/src/locales/fa/messages.json
+++ b/apps/web/src/locales/fa/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "فقط گاوصدوق سازمان مرتبط با $ORGANIZATION$ برون ریزی خواهد شد. موارد موجود در گاوصندوقهای فردی یا سایر سازمانها شامل نمیشوند.",
"placeholders": {
diff --git a/apps/web/src/locales/fi/messages.json b/apps/web/src/locales/fi/messages.json
index 9427a4e8e22..38671c12f79 100644
--- a/apps/web/src/locales/fi/messages.json
+++ b/apps/web/src/locales/fi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Vain organisaatioon $ORGANIZATION$ liitetyn holvin kohteet viedään. Yksityisen holvin ja muiden organisaatioiden kohteita ei sisällytetä.",
"placeholders": {
diff --git a/apps/web/src/locales/fil/messages.json b/apps/web/src/locales/fil/messages.json
index 414bbda91d8..0a07cf17526 100644
--- a/apps/web/src/locales/fil/messages.json
+++ b/apps/web/src/locales/fil/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/fr/messages.json b/apps/web/src/locales/fr/messages.json
index 95aa573f14b..e26b5ef1250 100644
--- a/apps/web/src/locales/fr/messages.json
+++ b/apps/web/src/locales/fr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Seuls les éléments individuels du coffre et les pièces jointes associées à $EMAIL$ seront exportés. Les éléments du coffre de l'organisation ne seront pas inclus",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Seul le coffre d'organisation associé à $ORGANIZATION$ sera exporté. Les éléments dans les coffres individuels ou d'autres organisations ne seront pas inclus.",
"placeholders": {
diff --git a/apps/web/src/locales/gl/messages.json b/apps/web/src/locales/gl/messages.json
index 38a94f9307f..6964a867768 100644
--- a/apps/web/src/locales/gl/messages.json
+++ b/apps/web/src/locales/gl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/he/messages.json b/apps/web/src/locales/he/messages.json
index 4df39c8496f..115482c021b 100644
--- a/apps/web/src/locales/he/messages.json
+++ b/apps/web/src/locales/he/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "רק כספת הארגון המשויכת עם $ORGANIZATION$ תיוצא. פריטים בכספת אישית או ארגונים אחרים לא יכללו.",
"placeholders": {
diff --git a/apps/web/src/locales/hi/messages.json b/apps/web/src/locales/hi/messages.json
index 1cab0dc843b..bdd11c6561f 100644
--- a/apps/web/src/locales/hi/messages.json
+++ b/apps/web/src/locales/hi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/hr/messages.json b/apps/web/src/locales/hr/messages.json
index 65c42a0cbf0..5ae2ddcfc0b 100644
--- a/apps/web/src/locales/hr/messages.json
+++ b/apps/web/src/locales/hr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Izvest će se samo stavke i privici osobnog trezora povezanog s $EMAIL$. Stavke organizacijskog trezora neće biti uključene",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Izvest će se samo organizacijski trezor povezan s $ORGANIZATION$. Stavke iz osobnih trezora i stavke iz drugih organizacija neće biti uključene.",
"placeholders": {
diff --git a/apps/web/src/locales/hu/messages.json b/apps/web/src/locales/hu/messages.json
index 3d6a2e258a4..b7a51e5048b 100644
--- a/apps/web/src/locales/hu/messages.json
+++ b/apps/web/src/locales/hu/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Csak $EMAIL$ email címmel társított személyes széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a szervezeti széf elemek.",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Csak$ORGANIZATION$ névvel társított szervezeti széf elemek kerülnek exportálásra. Ebbe nem kerülnek be a személyes és más szervezeti széf elemek.",
"placeholders": {
diff --git a/apps/web/src/locales/id/messages.json b/apps/web/src/locales/id/messages.json
index 22b4e3a6132..dc3ba77328f 100644
--- a/apps/web/src/locales/id/messages.json
+++ b/apps/web/src/locales/id/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/it/messages.json b/apps/web/src/locales/it/messages.json
index 0cbc425d588..853c580c82d 100644
--- a/apps/web/src/locales/it/messages.json
+++ b/apps/web/src/locales/it/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Solo la cassaforte dell'organizzazione associata a $ORGANIZATION$ sarà esportata. Elementi nelle casseforti individuali o in altre organizzazioni non saranno inclusi.",
"placeholders": {
diff --git a/apps/web/src/locales/ja/messages.json b/apps/web/src/locales/ja/messages.json
index 6860b30b360..d445184774c 100644
--- a/apps/web/src/locales/ja/messages.json
+++ b/apps/web/src/locales/ja/messages.json
@@ -474,7 +474,7 @@
"message": "フォルダーを編集"
},
"editWithName": {
- "message": "Edit $ITEM$: $NAME$",
+ "message": "$ITEM$: $NAME$ を編集",
"placeholders": {
"item": {
"content": "$1",
@@ -1202,7 +1202,7 @@
"message": "デバイスとは何ですか?"
},
"aDeviceIs": {
- "message": "A device is a unique installation of the Bitwarden app where you have logged in. Reinstalling, clearing app data, or clearing your cookies could result in a device appearing multiple times."
+ "message": "デバイスとは、あなたがログインしている Bitwarden アプリの一意なインストールです。 再インストールや、アプリデータの消去、 Cookie の消去をすると、同じデバイスが複数回表示される場合があります。"
},
"logInInitiated": {
"message": "ログイン開始"
@@ -1894,22 +1894,22 @@
"message": "新しいデバイスからのログイン"
},
"turnOffNewDeviceLoginProtection": {
- "message": "Turn off new device login protection"
+ "message": "新しいデバイスからのログイン保護をオフにする"
},
"turnOnNewDeviceLoginProtection": {
- "message": "Turn on new device login protection"
+ "message": "新しいデバイスからのログイン保護をオンにする"
},
"turnOffNewDeviceLoginProtectionModalDesc": {
- "message": "Proceed below to turn off the verification emails bitwarden sends when you login from a new device."
+ "message": "続行すると、新しいデバイスからログインしたときに Bitwarden が送信する認証メールをオフにします。"
},
"turnOnNewDeviceLoginProtectionModalDesc": {
- "message": "Proceed below to have bitwarden send you verification emails when you login from a new device."
+ "message": "続行すると、新しいデバイスからログインする際に Bitwarden から認証メールを送信します。"
},
"turnOffNewDeviceLoginProtectionWarning": {
- "message": "With new device login protection turned off, anyone with your master password can access your account from any device. To protect your account without verification emails, set up two-step login."
+ "message": "新しいデバイスからのログイン保護をオフにすると、マスターパスワードを持つ人は誰でも、どのデバイスからでもあなたのアカウントにアクセスできます。認証メールなしでアカウントを保護するには、2段階認証によるログインを設定してください。"
},
"accountNewDeviceLoginProtectionSaved": {
- "message": "New device login protection changes saved"
+ "message": "新しいデバイスからのログイン保護の変更が保存されました"
},
"sessionsDeauthorized": {
"message": "全てのセッションを無効化"
@@ -2175,7 +2175,7 @@
"message": "2段階認証を有効にすると Bitwarden アカウントから永久に閉め出されてしまうことがあります。リカバリーコードがあれば、通常の2段階認証プロバイダを使えなくなったとき (デバイスの紛失等) でもアカウントにアクセスできます。アカウントにアクセスできなくなっても Bitwarden はサポート出来ないため、リカバリーコードを書き出すか印刷し安全な場所で保管しておくことを推奨します。"
},
"yourSingleUseRecoveryCode": {
- "message": "Your single-use recovery code can be used to turn off two-step login in the event that you lose access to your two-step login provider. Bitwarden recommends you write down the recovery code and keep it in a safe place."
+ "message": "2段階認証プロバイダーへのアクセスを失った場合は、使い捨てのリカバリーコードを使用して2段階認証をオフにできます。 Bitwarden では、リカバリーコードを書き留めて安全な場所に保管することをお勧めしています。"
},
"viewRecoveryCode": {
"message": "リカバリーコードを確認"
@@ -2216,7 +2216,7 @@
"message": "管理"
},
"manageCollection": {
- "message": "Manage collection"
+ "message": "コレクションの管理"
},
"viewItems": {
"message": "アイテムを表示"
@@ -2429,7 +2429,7 @@
"message": "セキュリティキーの読み取り中に問題が発生しました。もう一度やり直して下さい。"
},
"twoFactorWebAuthnWarning1": {
- "message": "Due to platform limitations, WebAuthn cannot be used on all Bitwarden applications. You should set up another two-step login provider so that you can access your account when WebAuthn cannot be used."
+ "message": "プラットフォームの制限により、 WebAuthn は全ての Bitwarden アプリケーションで使用できるわけではありません。 WebAuthn が使用できない場合に備えて、他の2段階認証プロバイダーを設定しておくことをおすすめします。"
},
"twoFactorRecoveryYourCode": {
"message": "二段階認証のリカバリーコード"
@@ -3396,10 +3396,10 @@
}
},
"inviteSingleEmailDesc": {
- "message": "You have 1 invite remaining."
+ "message": "招待は残り 1 個です。"
},
"inviteZeroEmailDesc": {
- "message": "You have 0 invites remaining."
+ "message": "招待は残り 0 個です。"
},
"userUsingTwoStep": {
"message": "このユーザーはアカウントを保護するため二段階認証を利用しています。"
@@ -3853,7 +3853,7 @@
}
},
"unlinkedSso": {
- "message": "Unlinked SSO."
+ "message": "SSO のリンクを解除しました。"
},
"unlinkedSsoUser": {
"message": "ユーザー $ID$ にリンクされていない SSO",
@@ -3904,22 +3904,22 @@
"message": "デバイス"
},
"loginStatus": {
- "message": "Login status"
+ "message": "ログイン状態"
},
"firstLogin": {
- "message": "First login"
+ "message": "初回ログイン"
},
"trusted": {
- "message": "Trusted"
+ "message": "信頼済み"
},
"needsApproval": {
- "message": "Needs approval"
+ "message": "承認が必要"
},
"areYouTryingtoLogin": {
- "message": "Are you trying to log in?"
+ "message": "ログインしようとしていますか?"
},
"logInAttemptBy": {
- "message": "Login attempt by $EMAIL$",
+ "message": "$EMAIL$ によるログインの試行",
"placeholders": {
"email": {
"content": "$1",
@@ -3928,7 +3928,7 @@
}
},
"deviceType": {
- "message": "Device Type"
+ "message": "デバイスタイプ"
},
"ipAddress": {
"message": "IP アドレス"
@@ -3965,7 +3965,7 @@
"message": "たった今"
},
"requestedXMinutesAgo": {
- "message": "Requested $MINUTES$ minutes ago",
+ "message": "$MINUTES$ 分前に要求されました",
"placeholders": {
"minutes": {
"content": "$1",
@@ -4097,10 +4097,10 @@
"message": "サポートされていないブラウザを使用しています。ウェブ保管庫が正しく動作しないかもしれません。"
},
"youHaveAPendingLoginRequest": {
- "message": "You have a pending login request from another device."
+ "message": "別のデバイスからの保留中のログインリクエストがあります。"
},
"reviewLoginRequest": {
- "message": "Review login request"
+ "message": "ログインリクエストの内容を確認"
},
"freeTrialEndPromptCount": {
"message": "無料体験はあと $COUNT$ 日で終了します。",
@@ -4197,7 +4197,7 @@
"message": "もし通常の二段階認証の方法でアカウントにアクセスできなくなった場合、リカバリーコードにより全ての二段階認証プロバイダを無効化することができます。"
},
"logInBelowUsingYourSingleUseRecoveryCode": {
- "message": "Log in below using your single-use recovery code. This will turn off all two-step providers on your account."
+ "message": "使い捨てのリカバリーコードを使用して以下からログインしてください。これにより、アカウントのすべての2段階認証プロバイダーが無効になります。"
},
"recoverAccountTwoStep": {
"message": "二段階認証ログインの回復"
@@ -4509,7 +4509,7 @@
}
},
"reorderFieldUp": {
- "message": "$LABEL$ moved up, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ を上に移動しました。$INDEX$ / $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4526,7 +4526,7 @@
}
},
"reorderFieldDown": {
- "message": "$LABEL$ moved down, position $INDEX$ of $LENGTH$",
+ "message": "$LABEL$ を下に移動しました。$INDEX$ / $LENGTH$",
"placeholders": {
"label": {
"content": "$1",
@@ -4814,7 +4814,7 @@
"message": "マスターパスワードの強度に最低要件を設定する。"
},
"passwordStrengthScore": {
- "message": "Password strength score $SCORE$",
+ "message": "パスワードの強度スコア $SCORE$",
"placeholders": {
"score": {
"content": "$1",
@@ -5103,14 +5103,14 @@
"message": "組織の所有者および管理者は、このポリシーの執行から除外されます。"
},
"limitSendViews": {
- "message": "Limit views"
+ "message": "表示の制限"
},
"limitSendViewsHint": {
- "message": "No one can view this Send after the limit is reached.",
+ "message": "上限に達すると、誰もこの Send を表示できなくなります。",
"description": "Displayed under the limit views field on Send"
},
"limitSendViewsCount": {
- "message": "$ACCESSCOUNT$ views left",
+ "message": "残り $ACCESSCOUNT$ 回",
"description": "Displayed under the limit views field on Send",
"placeholders": {
"accessCount": {
@@ -5120,11 +5120,11 @@
}
},
"sendDetails": {
- "message": "Send details",
+ "message": "Send の詳細",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"sendTypeTextToShare": {
- "message": "Text to share"
+ "message": "共有するテキスト"
},
"sendTypeFile": {
"message": "ファイル"
@@ -5133,7 +5133,7 @@
"message": "テキスト"
},
"sendPasswordDescV3": {
- "message": "Add an optional password for recipients to access this Send.",
+ "message": "必要に応じて、受信者がこの Send にアクセスするためのパスワードを追加します。",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"createSend": {
@@ -5161,14 +5161,14 @@
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deleteSendPermanentConfirmation": {
- "message": "Are you sure you want to permanently delete this Send?",
+ "message": "この Send を完全に削除してもよろしいですか?",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"deletionDate": {
"message": "削除日時"
},
"deletionDateDescV2": {
- "message": "The Send will be permanently deleted on this date.",
+ "message": "この Send はこの期間ののち、完全に削除されます。",
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
},
"expirationDate": {
@@ -5218,7 +5218,7 @@
"message": "保留中の削除"
},
"hideTextByDefault": {
- "message": "Hide text by default"
+ "message": "デフォルトでテキストを隠す"
},
"expired": {
"message": "期限切れ"
@@ -5689,7 +5689,7 @@
"message": "削除と有効期限の保存中にエラーが発生しました。"
},
"hideYourEmail": {
- "message": "Hide your email address from viewers."
+ "message": "閲覧者にメールアドレスを見せないようにします。"
},
"webAuthnFallbackMsg": {
"message": "二段階認証を確認するには、下のボタンをクリックしてください。"
@@ -5698,10 +5698,10 @@
"message": "WebAuthn の認証"
},
"readSecurityKey": {
- "message": "Read security key"
+ "message": "セキュリティキーの読み取り"
},
"awaitingSecurityKeyInteraction": {
- "message": "Awaiting security key interaction..."
+ "message": "セキュリティキーとの通信を待ち受け中…"
},
"webAuthnNotSupported": {
"message": "WebAuthn はこのブラウザではサポートされていません。"
@@ -5917,17 +5917,17 @@
"message": "エラー"
},
"decryptionError": {
- "message": "Decryption error"
+ "message": "復号エラー"
},
"couldNotDecryptVaultItemsBelow": {
- "message": "Bitwarden could not decrypt the vault item(s) listed below."
+ "message": "Bitwarden は以下の保管庫のアイテムを復号できませんでした。"
},
"contactCSToAvoidDataLossPart1": {
- "message": "Contact customer success",
+ "message": "カスタマーサクセスに問い合わせて、",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"contactCSToAvoidDataLossPart2": {
- "message": "to avoid additional data loss.",
+ "message": "さらなるデータ損失を回避してください。",
"description": "This is part of a larger sentence. The full sentence will read 'Contact customer success to avoid additional data loss.'"
},
"accountRecoveryManageUsers": {
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "$EMAIL$ に関連付けられた個人用保管庫のアイテムのみが、添付ファイルを含めてエクスポートされます。組織用保管庫のアイテムは含まれません。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "$ORGANIZATION$ に関連付けられた組織保管庫のみがエクスポートされます。個々の保管庫または他の組織にあるアイテムは含まれません。",
"placeholders": {
@@ -6845,7 +6854,7 @@
"message": "ドメインに設定されたキャッチオール受信トレイを使用します。"
},
"useThisEmail": {
- "message": "Use this email"
+ "message": "このメールアドレスを使う"
},
"random": {
"message": "ランダム",
@@ -6997,7 +7006,7 @@
}
},
"forwaderInvalidOperation": {
- "message": "$SERVICENAME$ refused your request. Please contact your service provider for assistance.",
+ "message": "$SERVICENAME$ はリクエストを拒否しました。サービスプロバイダーにお問い合わせください。",
"description": "Displayed when the user is forbidden from using the API by the forwarding service.",
"placeholders": {
"servicename": {
@@ -7007,7 +7016,7 @@
}
},
"forwaderInvalidOperationWithMessage": {
- "message": "$SERVICENAME$ refused your request: $ERRORMESSAGE$",
+ "message": "$SERVICENAME$ があなたのリクエストを拒否しました: $ERRORMESSAGE$",
"description": "Displayed when the user is forbidden from using the API by the forwarding service with an error message.",
"placeholders": {
"servicename": {
@@ -10159,7 +10168,7 @@
}
},
"userLeftOrganization": {
- "message": "User $ID$ left organization",
+ "message": "ユーザー $ID$ が組織から脱退しました",
"placeholders": {
"id": {
"content": "$1",
@@ -10168,7 +10177,7 @@
}
},
"suspendedOrganizationTitle": {
- "message": "The $ORGANIZATION$ is suspended",
+ "message": "$ORGANIZATION$ は一時停止されています",
"placeholders": {
"organization": {
"content": "$1",
@@ -10177,25 +10186,25 @@
}
},
"suspendedUserOrgMessage": {
- "message": "Contact your organization owner for assistance."
+ "message": "組織の所有者にお問い合わせください。"
},
"suspendedOwnerOrgMessage": {
- "message": "To regain access to your organization, add a payment method."
+ "message": "組織へのアクセスを取り戻すには、支払い方法を追加してください。"
},
"deleteMembers": {
- "message": "Delete members"
+ "message": "メンバーを削除"
},
"noSelectedMembersApplicable": {
- "message": "This action is not applicable to any of the selected members."
+ "message": "この操作は、選択されたメンバーには適用できません。"
},
"deletedSuccessfully": {
- "message": "Deleted successfully"
+ "message": "正常に削除されました"
},
"freeFamiliesSponsorship": {
- "message": "Remove Free Bitwarden Families sponsorship"
+ "message": "Bitwarden ファミリープランの無償利用へのスポンサーを解除"
},
"freeFamiliesSponsorshipPolicyDesc": {
- "message": "Do not allow members to redeem a Families plan through this organization."
+ "message": "メンバーがこの組織を通じてファミリープランを引き換えられないようにします。"
},
"verifyBankAccountWithStatementDescriptorWarning": {
"message": "Payment with a bank account is only available to customers in the United States. You will be required to verify your bank account. We will make a micro-deposit within the next 1-2 business days. Enter the statement descriptor code from this deposit on the organization's billing page to verify the bank account. Failure to verify the bank account will result in a missed payment and your subscription being suspended."
@@ -10545,6 +10554,6 @@
"message": "These events are examples only and do not reflect real events within your Bitwarden organization."
},
"cannotCreateCollection": {
- "message": "Free organizations may have up to 2 collections. Upgrade to a paid plan to add more collections."
+ "message": "無料版の組織ではコレクションは 2 つまでです。さらにコレクションを追加するには有料プランにアップグレードしてください。"
}
}
diff --git a/apps/web/src/locales/ka/messages.json b/apps/web/src/locales/ka/messages.json
index f5f4bfb3ae1..5ace08617ff 100644
--- a/apps/web/src/locales/ka/messages.json
+++ b/apps/web/src/locales/ka/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/km/messages.json b/apps/web/src/locales/km/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/km/messages.json
+++ b/apps/web/src/locales/km/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/kn/messages.json b/apps/web/src/locales/kn/messages.json
index dbf2cab3bb5..8132c9a2db5 100644
--- a/apps/web/src/locales/kn/messages.json
+++ b/apps/web/src/locales/kn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ko/messages.json b/apps/web/src/locales/ko/messages.json
index 1f3bbda3736..6e59f0ccf88 100644
--- a/apps/web/src/locales/ko/messages.json
+++ b/apps/web/src/locales/ko/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "$ORGANIZATION$ 조직과 연관된 조직 보관함만 내보내기됩니다. 개인 보관함이나 다른 조직의 항목은 포함되지 않습니다.",
"placeholders": {
diff --git a/apps/web/src/locales/lv/messages.json b/apps/web/src/locales/lv/messages.json
index 7c8c3b2831e..e59f6078bf5 100644
--- a/apps/web/src/locales/lv/messages.json
+++ b/apps/web/src/locales/lv/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tiks izdoti tikai atsevišķi glabātavas vienumi, tajā skaitā pielikumi, kas ir saistīti ar $EMAIL$. Apvienības glabātavas vienumi netiks iekļauti",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Tiks izgūta tikai apvienības glabātava, kas ir saistīta ar $ORGANIZATION$. Atsevišķu glabātavu vai citu apvienību vienumi netiks iekļauti.",
"placeholders": {
diff --git a/apps/web/src/locales/ml/messages.json b/apps/web/src/locales/ml/messages.json
index 46ce3b62533..10ae27a2139 100644
--- a/apps/web/src/locales/ml/messages.json
+++ b/apps/web/src/locales/ml/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/mr/messages.json b/apps/web/src/locales/mr/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/mr/messages.json
+++ b/apps/web/src/locales/mr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/my/messages.json b/apps/web/src/locales/my/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/my/messages.json
+++ b/apps/web/src/locales/my/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/nb/messages.json b/apps/web/src/locales/nb/messages.json
index 24285b8cb9f..bc36ba82ff1 100644
--- a/apps/web/src/locales/nb/messages.json
+++ b/apps/web/src/locales/nb/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ne/messages.json b/apps/web/src/locales/ne/messages.json
index 8183efd5f9b..c947c0bb716 100644
--- a/apps/web/src/locales/ne/messages.json
+++ b/apps/web/src/locales/ne/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/nl/messages.json b/apps/web/src/locales/nl/messages.json
index 6b752b5a195..1bc731e3ceb 100644
--- a/apps/web/src/locales/nl/messages.json
+++ b/apps/web/src/locales/nl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exporteert alleen de persoonlijke kluis-items, inclusief attachments, gerelateerd aan $EMAIL$. Geen kluis-items van de organisatie",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exporteert alleen de organisatiekluis van $ORGANIZATION$. Geen persoonlijke kluis-items of items van andere organisaties.",
"placeholders": {
diff --git a/apps/web/src/locales/nn/messages.json b/apps/web/src/locales/nn/messages.json
index 23d4d3b3089..ae64568bd79 100644
--- a/apps/web/src/locales/nn/messages.json
+++ b/apps/web/src/locales/nn/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/or/messages.json b/apps/web/src/locales/or/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/or/messages.json
+++ b/apps/web/src/locales/or/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/pl/messages.json b/apps/web/src/locales/pl/messages.json
index 9ef5c95bf86..f793b249148 100644
--- a/apps/web/src/locales/pl/messages.json
+++ b/apps/web/src/locales/pl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Tylko poszczególne elementy sejfu łącznie z załącznikami powiązanymi z $EMAIL$ zostaną wyeksportowane. Elementy sejfu organizacji nie będą dołączone",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Tylko sejf organizacji powiązany z $ORGANIZATION$ zostanie wyeksportowany. Pozycje w poszczególnych sejfach lub innych organizacji nie będą uwzględnione.",
"placeholders": {
diff --git a/apps/web/src/locales/pt_BR/messages.json b/apps/web/src/locales/pt_BR/messages.json
index ea43fd4eae6..3ac5afa3110 100644
--- a/apps/web/src/locales/pt_BR/messages.json
+++ b/apps/web/src/locales/pt_BR/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Apenas o cofre da organização associado com $ORGANIZATION$ será exportado. Itens do cofre pessoal e itens de outras organizações não serão incluídos.",
"placeholders": {
diff --git a/apps/web/src/locales/pt_PT/messages.json b/apps/web/src/locales/pt_PT/messages.json
index 9bbda05c93f..1cd3f9c9b1b 100644
--- a/apps/web/src/locales/pt_PT/messages.json
+++ b/apps/web/src/locales/pt_PT/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Apenas os itens individuais do cofre, incluindo os anexos associados a $EMAIL$, serão exportados. Os itens do cofre da organização não serão incluídos",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Apenas o cofre da organização associado a $ORGANIZATION$ será exportado. Os itens em cofres individuais ou noutras organizações não serão incluídos.",
"placeholders": {
diff --git a/apps/web/src/locales/ro/messages.json b/apps/web/src/locales/ro/messages.json
index c57ad26d251..f67c9550655 100644
--- a/apps/web/src/locales/ro/messages.json
+++ b/apps/web/src/locales/ro/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/ru/messages.json b/apps/web/src/locales/ru/messages.json
index a31d13d227e..2bdaeb63206 100644
--- a/apps/web/src/locales/ru/messages.json
+++ b/apps/web/src/locales/ru/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Будут экспортированы только отдельные элементы хранилища, включая вложения, связанные с $EMAIL$. Элементы хранилища организации включены не будут",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Будет экспортировано только хранилище организации, связанное с $ORGANIZATION$. Элементы из личных хранилищ и из других организаций включены не будут.",
"placeholders": {
diff --git a/apps/web/src/locales/si/messages.json b/apps/web/src/locales/si/messages.json
index fc79da1352b..bbb01d1aa49 100644
--- a/apps/web/src/locales/si/messages.json
+++ b/apps/web/src/locales/si/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sk/messages.json b/apps/web/src/locales/sk/messages.json
index fdf57456a4c..339a73bbffb 100644
--- a/apps/web/src/locales/sk/messages.json
+++ b/apps/web/src/locales/sk/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Exportované budú iba položy osobného trezora spojené s $EMAIL$. Položky trezora organizácie nebudú zahrnuté v exporte",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Exportované budú iba položky trezora organizácie spojené s $ORGANIZATION$. Položky osobného trezora a položky z iných organizácií nebudú zahrnuté.",
"placeholders": {
diff --git a/apps/web/src/locales/sl/messages.json b/apps/web/src/locales/sl/messages.json
index 1652841b650..d14b4f13ecc 100644
--- a/apps/web/src/locales/sl/messages.json
+++ b/apps/web/src/locales/sl/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sr/messages.json b/apps/web/src/locales/sr/messages.json
index 005fa7602b5..2ac4a713189 100644
--- a/apps/web/src/locales/sr/messages.json
+++ b/apps/web/src/locales/sr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Биће извезен само сеф организације повезан са $ORGANIZATION$. Ставке у појединачним сефовима или другим организацијама неће бити укључене.",
"placeholders": {
diff --git a/apps/web/src/locales/sr_CS/messages.json b/apps/web/src/locales/sr_CS/messages.json
index d141d8301fc..d84f3192dea 100644
--- a/apps/web/src/locales/sr_CS/messages.json
+++ b/apps/web/src/locales/sr_CS/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/sv/messages.json b/apps/web/src/locales/sv/messages.json
index f7d6414c36c..e4cc2cdeb67 100644
--- a/apps/web/src/locales/sv/messages.json
+++ b/apps/web/src/locales/sv/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/te/messages.json b/apps/web/src/locales/te/messages.json
index f334131b69b..59eac9b372b 100644
--- a/apps/web/src/locales/te/messages.json
+++ b/apps/web/src/locales/te/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/th/messages.json b/apps/web/src/locales/th/messages.json
index 7d2685807b3..3b05ffc9000 100644
--- a/apps/web/src/locales/th/messages.json
+++ b/apps/web/src/locales/th/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/tr/messages.json b/apps/web/src/locales/tr/messages.json
index 030fd0dd9b2..ef8701ce4b1 100644
--- a/apps/web/src/locales/tr/messages.json
+++ b/apps/web/src/locales/tr/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Yalnızca $EMAIL$ ile ilişkili kişisel kasadaki kayıtlar ve dosyalar dışa aktarılacaktır. Kuruluş kasasındaki kayıtlar dahil edilmeyecektir",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/uk/messages.json b/apps/web/src/locales/uk/messages.json
index 7d9eb316821..e175fe99db8 100644
--- a/apps/web/src/locales/uk/messages.json
+++ b/apps/web/src/locales/uk/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Буде експортовано лише сховище організації, пов'язане з $ORGANIZATION$. Записи особистих сховищ або інших організацій не будуть включені.",
"placeholders": {
diff --git a/apps/web/src/locales/vi/messages.json b/apps/web/src/locales/vi/messages.json
index f0a0215f823..f7e8f9f4037 100644
--- a/apps/web/src/locales/vi/messages.json
+++ b/apps/web/src/locales/vi/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "Only the organization vault associated with $ORGANIZATION$ will be exported. Items in individual vaults or other organizations will not be included.",
"placeholders": {
diff --git a/apps/web/src/locales/zh_CN/messages.json b/apps/web/src/locales/zh_CN/messages.json
index 00bf9bc5973..6bb79aa1971 100644
--- a/apps/web/src/locales/zh_CN/messages.json
+++ b/apps/web/src/locales/zh_CN/messages.json
@@ -2958,7 +2958,7 @@
"description": "Past tense status of an invoice. ex. Paid or unpaid."
},
"unpaid": {
- "message": "待支付",
+ "message": "未支付",
"description": "Past tense status of an invoice. ex. Paid or unpaid."
},
"transactions": {
@@ -4799,7 +4799,7 @@
"message": "您必须至少选择一个集合。"
},
"couldNotChargeCardPayInvoice": {
- "message": "我们无法从您的支付卡中扣款。请查看并支付下面列出的待支付账单。"
+ "message": "我们无法从您的支付卡中扣款。请查看并支付下面列出的未支付账单。"
},
"minLength": {
"message": "最小长度"
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "仅会导出与 $EMAIL$ 关联的个人密码库项目(包括附件)。不包括组织密码库项目。",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "仅会导出与 $ORGANIZATION$ 关联的组织密码库数据。不包括个人密码库和其他组织中的项目。",
"placeholders": {
diff --git a/apps/web/src/locales/zh_TW/messages.json b/apps/web/src/locales/zh_TW/messages.json
index beca8f74485..c23def1464d 100644
--- a/apps/web/src/locales/zh_TW/messages.json
+++ b/apps/web/src/locales/zh_TW/messages.json
@@ -6741,6 +6741,15 @@
}
}
},
+ "exportingIndividualVaultWithAttachmentsDescription": {
+ "message": "Only the individual vault items including attachments associated with $EMAIL$ will be exported. Organization vault items will not be included",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "name@example.com"
+ }
+ }
+ },
"exportingOrganizationVaultDesc": {
"message": "僅匯出與 $ORGANIZATION$ 相關的組織密碼庫項目。個人密碼庫項目或其他組織中的項目將不包括在内。",
"placeholders": {
From 7ed21453937eb18ed9af96b9b57342f8f54f5f7d Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Fri, 28 Mar 2025 16:20:36 +0100
Subject: [PATCH 38/66] Set correct filename extensions on vault-export
(#14018)
Co-authored-by: Daniel James Smith
---
.../src/services/individual-vault-export.service.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
index 0fc1f336b90..765de042d32 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
@@ -81,7 +81,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: await this.buildPasswordExport(exportVault.data, password),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "encrypted_json"),
} as ExportedVaultAsString;
}
@@ -126,7 +126,7 @@ export class IndividualVaultExportService
return {
type: "application/zip",
data: blobData,
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsBlob;
}
@@ -185,14 +185,14 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: this.buildCsvExport(decFolders, decCiphers),
- fileName: ExportHelper.getFileName("csv"),
+ fileName: ExportHelper.getFileName("", "csv"),
} as ExportedVaultAsString;
}
return {
type: "text/plain",
data: this.buildJsonExport(decFolders, decCiphers),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsString;
}
@@ -250,7 +250,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: JSON.stringify(jsonDoc, null, " "),
- fileName: ExportHelper.getFileName("json"),
+ fileName: ExportHelper.getFileName("", "json"),
} as ExportedVaultAsString;
}
From 6204e2a092c50ed192532a0caac2cbbe2283ba4b Mon Sep 17 00:00:00 2001
From: Vicki League
Date: Fri, 28 Mar 2025 13:41:53 -0400
Subject: [PATCH 39/66] [CL-550] Update cipher form story (#14019)
---
libs/vault/src/cipher-form/cipher-form.mdx | 3 ++
.../src/cipher-form/cipher-form.stories.ts | 50 +++++++++++++++----
2 files changed, 44 insertions(+), 9 deletions(-)
diff --git a/libs/vault/src/cipher-form/cipher-form.mdx b/libs/vault/src/cipher-form/cipher-form.mdx
index ed2e799b9f3..658fcd38d11 100644
--- a/libs/vault/src/cipher-form/cipher-form.mdx
+++ b/libs/vault/src/cipher-form/cipher-form.mdx
@@ -12,6 +12,9 @@ It is configured via a `CipherFormConfig` object that is passed to the component
create it. A default implementation of the `CipherFormConfigService` exists in the
`@bitwarden/vault` library.
+The cipher form has a slot for `attachment-button`, which should be included when the form is in
+`edit` mode.
+
diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts
index 5d6464b4c79..52d70e8652a 100644
--- a/libs/vault/src/cipher-form/cipher-form.stories.ts
+++ b/libs/vault/src/cipher-form/cipher-form.stories.ts
@@ -29,7 +29,7 @@ import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
-import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components";
+import { AsyncActionsModule, ButtonModule, ItemModule, ToastService } from "@bitwarden/components";
import {
CipherFormConfig,
CipherFormGenerationService,
@@ -131,7 +131,7 @@ export default {
component: CipherFormComponent,
decorators: [
moduleMetadata({
- imports: [CipherFormModule, AsyncActionsModule, ButtonModule],
+ imports: [CipherFormModule, AsyncActionsModule, ButtonModule, ItemModule],
providers: [
{
provide: CipherFormService,
@@ -246,7 +246,7 @@ export default {
type Story = StoryObj;
-export const Default: Story = {
+export const Add: Story = {
render: (args) => {
return {
props: {
@@ -254,15 +254,28 @@ export const Default: Story = {
...args,
},
template: /*html*/ `
-
- Submit
+
`,
};
},
};
export const Edit: Story = {
- ...Default,
+ render: (args) => {
+ return {
+ props: {
+ onSave: actionsData.onSave,
+ ...args,
+ },
+ template: /*html*/ `
+
+
+ Attachments
+
+
+ `,
+ };
+ },
args: {
config: {
...defaultConfig,
@@ -273,7 +286,7 @@ export const Edit: Story = {
};
export const PartialEdit: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
@@ -284,7 +297,7 @@ export const PartialEdit: Story = {
};
export const Clone: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
@@ -294,8 +307,27 @@ export const Clone: Story = {
},
};
+export const WithSubmitButton: Story = {
+ render: (args) => {
+ return {
+ props: {
+ onSave: actionsData.onSave,
+ ...args,
+ },
+ template: /*html*/ `
+
+
+
+
+ Submit
+
+ `,
+ };
+ },
+};
+
export const NoPersonalOwnership: Story = {
- ...Default,
+ ...Add,
args: {
config: {
...defaultConfig,
From d5f033efa2a50e82780dd140b0431eb7bb455def Mon Sep 17 00:00:00 2001
From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com>
Date: Fri, 28 Mar 2025 12:51:20 -0500
Subject: [PATCH 40/66] refactor(auth): [PM-9179] remove deprecated
TwoFactorComponents
Remove deprecated TwoFactorComponentsV1 and TwoFactorOptionsComponentV1 components, related functionality (unauthUiRefreshSwap) and orphaned styles/translation messages.
---
apps/browser/src/_locales/en/messages.json | 36 --
.../two-factor-options-v1.component.html | 29 -
.../popup/two-factor-options-v1.component.ts | 56 --
.../auth/popup/two-factor-v1.component.html | 196 -------
.../src/auth/popup/two-factor-v1.component.ts | 260 ---------
apps/browser/src/popup/app-routing.module.ts | 53 +-
apps/browser/src/popup/app.module.ts | 4 -
apps/browser/src/popup/scss/misc.scss | 10 -
apps/desktop/src/app/app-routing.module.ts | 39 +-
apps/desktop/src/app/app.module.ts | 4 -
.../auth/two-factor-options-v1.component.html | 33 --
.../auth/two-factor-options-v1.component.ts | 24 -
.../src/auth/two-factor-v1.component.html | 175 ------
.../src/auth/two-factor-v1.component.ts | 190 -------
apps/desktop/src/locales/en/messages.json | 27 -
apps/desktop/src/scss/pages.scss | 2 -
.../auth/two-factor-options-v1.component.html | 45 --
.../auth/two-factor-options-v1.component.ts | 52 --
.../src/app/auth/two-factor-v1.component.html | 106 ----
.../src/app/auth/two-factor-v1.component.ts | 164 ------
apps/web/src/app/oss-routing.module.ts | 69 +--
.../src/app/shared/loose-components.module.ts | 6 -
apps/web/src/locales/en/messages.json | 28 +-
.../two-factor-options-v1.component.ts | 44 --
.../two-factor-v1.component.spec.ts | 505 -----------------
.../components/two-factor-v1.component.ts | 514 ------------------
.../functions/unauth-ui-refresh-route-swap.ts | 36 --
27 files changed, 55 insertions(+), 2652 deletions(-)
delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.html
delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.ts
delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.html
delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.ts
delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.html
delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.ts
delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.html
delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.ts
delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.html
delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.ts
delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.html
delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-options-v1.component.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.spec.ts
delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.ts
delete mode 100644 libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 2c940ccdf5a..8c47db0d331 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -1391,24 +1391,12 @@
"premiumRequiredDesc": {
"message": "A Premium membership is required to use this feature."
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
"authenticationTimeout": {
"message": "Authentication timeout"
},
"authenticationSessionTimedOut": {
"message": "The authentication session timed out. Please restart the login process."
},
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -1418,18 +1406,9 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -1437,18 +1416,9 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
- "webAuthnNewTab": {
- "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab."
- },
- "webAuthnNewTabOpen": {
- "message": "Open new tab"
- },
"openInNewTab": {
"message": "Open in new tab"
},
@@ -3848,15 +3818,9 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredForAccount": {
"message": "Duo two-step login is required for your account."
},
- "popoutTheExtensionToCompleteLogin": {
- "message": "Popout the extension to complete login."
- },
"popoutExtension": {
"message": "Popout extension"
},
diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.html b/apps/browser/src/auth/popup/two-factor-options-v1.component.html
deleted file mode 100644
index f25944aba65..00000000000
--- a/apps/browser/src/auth/popup/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
- {{ "close" | i18n }}
-
-
- {{ "twoStepOptions" | i18n }}
-
-
-
-
-
-
-
- {{ p.name }}
- {{ p.description }}
-
-
- {{ "recoveryCodeTitle" | i18n }}
- {{ "recoveryCodeDesc" | i18n }}
-
-
-
-
diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts b/apps/browser/src/auth/popup/two-factor-options-v1.component.ts
deleted file mode 100644
index 0c71421fc04..00000000000
--- a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Component } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import {
- TwoFactorProviderDetails,
- TwoFactorService,
-} from "@bitwarden/common/auth/abstractions/two-factor.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";
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponent {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- private activatedRoute: ActivatedRoute,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-
- close() {
- this.navigateTo2FA();
- }
-
- override async choose(p: TwoFactorProviderDetails) {
- await super.choose(p);
- await this.twoFactorService.setSelectedProvider(p.type);
-
- this.navigateTo2FA();
- }
-
- navigateTo2FA() {
- const sso = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true";
-
- if (sso) {
- // Persist SSO flag back to the 2FA comp if it exists
- // in order for successful login logic to work properly for
- // SSO + 2FA in browser extension
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa"], { queryParams: { sso: true } });
- } else {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa"]);
- }
- }
-}
diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.html b/apps/browser/src/auth/popup/two-factor-v1.component.html
deleted file mode 100644
index 126b0ea5a99..00000000000
--- a/apps/browser/src/auth/popup/two-factor-v1.component.html
+++ /dev/null
@@ -1,196 +0,0 @@
-
diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.ts b/apps/browser/src/auth/popup/two-factor-v1.component.ts
deleted file mode 100644
index 884e42bf73a..00000000000
--- a/apps/browser/src/auth/popup/two-factor-v1.component.ts
+++ /dev/null
@@ -1,260 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-import { Subject, Subscription, firstValueFrom } from "rxjs";
-import { filter, first, takeUntil } from "rxjs/operators";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
-import { DialogService, ToastService } from "@bitwarden/components";
-
-import { BrowserApi } from "../../platform/browser/browser-api";
-import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service";
-import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
-
-import { closeTwoFactorAuthWebAuthnPopout } from "./utils/auth-popout-window";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy {
- private destroy$ = new Subject();
- inPopout = BrowserPopupUtils.inPopout(window);
-
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- private syncService: SyncService,
- environmentService: EnvironmentService,
- stateService: StateService,
- route: ActivatedRoute,
- private messagingService: MessagingService,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- configService: ConfigService,
- ssoLoginService: SsoLoginServiceAbstraction,
- private dialogService: DialogService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- @Inject(WINDOW) protected win: Window,
- private browserMessagingApi: ZonedMessageListenerService,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLogin = async () => {
- // 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
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTde = async () => {
- // 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
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTdeNavigate = async () => {
- this.win.close();
- };
-
- this.successRoute = "/tabs/vault";
- // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe
- this.webAuthnNewTab = true;
- }
-
- async ngOnInit() {
- if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
- // WebAuthn fallback response
- this.selectedProviderType = TwoFactorProviderType.WebAuthn;
- this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
- this.onSuccessfulLogin = async () => {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.syncService.fullSync(true);
- this.messagingService.send("reloadPopup");
- window.close();
- };
- this.remember = this.route.snapshot.paramMap.get("remember") === "true";
- await this.doSubmit();
- return;
- }
-
- await super.ngOnInit();
- if (this.selectedProviderType == null) {
- return;
- }
-
- // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
- // than usual to avoid cutting off the dialog.
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
- document.body.classList.add("linux-webauthn");
- }
-
- if (
- this.selectedProviderType === TwoFactorProviderType.Email &&
- BrowserPopupUtils.inPopup(window)
- ) {
- const confirmed = await this.dialogService.openSimpleDialog({
- title: { key: "warning" },
- content: { key: "popup2faCloseMessage" },
- type: "warning",
- });
- if (confirmed) {
- // 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
- BrowserPopupUtils.openCurrentPagePopout(window);
- }
- }
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
- this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
- if (qParams.sso === "true") {
- this.onSuccessfulLogin = async () => {
- // This is not awaited so we don't pause the application while the sync is happening.
- // This call is executed by the service that lives in the background script so it will continue
- // the sync even if this tab closes.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.syncService.fullSync(true);
-
- // Force sidebars (FF && Opera) to reload while exempting current window
- // because we are just going to close the current window.
- BrowserApi.reloadOpenWindows(true);
-
- // We don't need this window anymore because the intent is for the user to be left
- // on the web vault screen which tells them to continue in the browser extension (sidebar or popup)
- await closeTwoFactorAuthWebAuthnPopout();
- };
- }
- });
- }
-
- async ngOnDestroy() {
- this.destroy$.next();
- this.destroy$.complete();
-
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
- document.body.classList.remove("linux-webauthn");
- }
- super.ngOnDestroy();
- }
-
- anotherMethod() {
- const sso = this.route.snapshot.queryParamMap.get("sso") === "true";
-
- if (sso) {
- // We must persist this so when the user returns to the 2FA comp, the
- // proper onSuccessfulLogin logic is executed.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa-options"], { queryParams: { sso: true } });
- } else {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["2fa-options"]);
- }
- }
-
- async popoutCurrentPage() {
- await BrowserPopupUtils.openCurrentPagePopout(window);
- }
-
- async isLinux() {
- return (await BrowserApi.getPlatformInfo()).os === "linux";
- }
-
- duoResultSubscription: Subscription;
- protected override setupDuoResultListener() {
- if (!this.duoResultSubscription) {
- this.duoResultSubscription = this.browserMessagingApi
- .messageListener$()
- .pipe(
- filter((msg: any) => msg.command === "duoResult"),
- takeUntil(this.destroy$),
- )
- .subscribe((msg: { command: string; code: string; state: string }) => {
- this.token = msg.code + "|" + msg.state;
- // This floating promise is intentional. We don't need to await the submit + awaiting in a subscription is not recommended.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.submit();
- });
- }
- }
-
- override async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- const duoHandOffMessage = {
- title: this.i18nService.t("youSuccessfullyLoggedIn"),
- message: this.i18nService.t("youMayCloseThisWindow"),
- isCountdown: false,
- };
-
- // we're using the connector here as a way to set a cookie with translations
- // before continuing to the duo frameless url
- const env = await firstValueFrom(this.environmentService.environment$);
- const launchUrl =
- env.getWebVaultUrl() +
- "/duo-redirect-connector.html" +
- "?duoFramelessUrl=" +
- encodeURIComponent(this.duoFramelessUrl) +
- "&handOffMessage=" +
- encodeURIComponent(JSON.stringify(duoHandOffMessage));
- this.platformUtilsService.launchUri(launchUrl);
- }
-}
diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts
index e73f56fa2f6..9948b450e17 100644
--- a/apps/browser/src/popup/app-routing.module.ts
+++ b/apps/browser/src/popup/app-routing.module.ts
@@ -7,7 +7,6 @@ import {
EnvironmentSelectorRouteData,
ExtensionDefaultOverlayPosition,
} from "@bitwarden/angular/auth/components/environment-selector.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
activeAuthGuard,
authGuard,
@@ -59,8 +58,6 @@ import {
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { Fido2Component } from "../autofill/popup/fido2/fido2.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
@@ -142,32 +139,6 @@ const routes: Routes = [
canActivate: [fido2AuthGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- ExtensionAnonLayoutWrapperComponent,
- {
- path: "2fa",
- canActivate: [unauthGuardFn(unauthRouteOverrides)],
- data: { elevation: 1 } satisfies RouteDataProperties,
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- ],
- data: {
- elevation: 1,
- pageTitle: {
- key: "verifyYourIdentity",
- },
- showBackButton: true,
- } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
- },
- ),
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
@@ -191,12 +162,6 @@ const routes: Routes = [
},
],
},
- {
- path: "2fa-options",
- component: TwoFactorOptionsComponentV1,
- canActivate: [unauthGuardFn(unauthRouteOverrides)],
- data: { elevation: 1 } satisfies RouteDataProperties,
- },
{
path: "device-verification",
component: ExtensionAnonLayoutWrapperComponent,
@@ -371,7 +336,6 @@ const routes: Routes = [
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
},
-
{
path: "",
component: ExtensionAnonLayoutWrapperComponent,
@@ -567,6 +531,23 @@ const routes: Routes = [
},
],
},
+ {
+ path: "2fa",
+ canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ ],
+ data: {
+ elevation: 1,
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ showBackButton: true,
+ } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData,
+ },
],
},
{
diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts
index 80cffa03b17..b2542679e06 100644
--- a/apps/browser/src/popup/app.module.ts
+++ b/apps/browser/src/popup/app.module.ts
@@ -25,8 +25,6 @@ import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component";
import { SsoComponentV1 } from "../auth/popup/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
import { AutofillComponent } from "../autofill/popup/settings/autofill.component";
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
@@ -93,8 +91,6 @@ import "../platform/popup/locales";
SetPasswordComponent,
SsoComponentV1,
TabsV2Component,
- TwoFactorComponentV1,
- TwoFactorOptionsComponentV1,
UpdateTempPasswordComponent,
UserVerificationComponent,
VaultTimeoutInputComponent,
diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss
index d1308d26180..8aace90d0a6 100644
--- a/apps/browser/src/popup/scss/misc.scss
+++ b/apps/browser/src/popup/scss/misc.scss
@@ -103,16 +103,6 @@ p.lead {
margin: 0 !important;
}
-.no-vmargin {
- margin-top: 0 !important;
- margin-bottom: 0 !important;
-}
-
-.no-vpad {
- padding-top: 0 !important;
- padding-bottom: 0 !important;
-}
-
.display-block {
display: block !important;
}
diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts
index 3bb130d321d..cd5064a87e4 100644
--- a/apps/desktop/src/app/app-routing.module.ts
+++ b/apps/desktop/src/app/app-routing.module.ts
@@ -6,7 +6,6 @@ import {
DesktopDefaultOverlayPosition,
EnvironmentSelectorComponent,
} from "@bitwarden/angular/auth/components/environment-selector.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
@@ -53,7 +52,6 @@ import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.compo
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VaultComponent } from "../vault/app/vault/vault.component";
@@ -76,28 +74,6 @@ const routes: Routes = [
children: [], // Children lets us have an empty component.
canActivate: [redirectGuard({ loggedIn: "/vault", loggedOut: "/login", locked: "/lock" })],
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- AnonLayoutWrapperComponent,
- {
- path: "2fa",
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- ),
{
path: "authentication-timeout",
component: AnonLayoutWrapperComponent,
@@ -360,6 +336,21 @@ const routes: Routes = [
},
} satisfies AnonLayoutWrapperData,
},
+ {
+ path: "2fa",
+ canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ ],
+ data: {
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ } satisfies RouteDataProperties & AnonLayoutWrapperData,
+ },
],
},
];
diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts
index b1b2864af5a..b717afe4a41 100644
--- a/apps/desktop/src/app/app.module.ts
+++ b/apps/desktop/src/app/app.module.ts
@@ -16,8 +16,6 @@ import { LoginModule } from "../auth/login/login.module";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { SsoComponentV1 } from "../auth/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { SshAgentService } from "../autofill/services/ssh-agent.service";
import { PremiumComponent } from "../billing/app/accounts/premium.component";
@@ -78,9 +76,7 @@ import { SharedModule } from "./shared/shared.module";
SetPasswordComponent,
SettingsComponent,
ShareComponent,
- TwoFactorComponentV1,
SsoComponentV1,
- TwoFactorOptionsComponentV1,
UpdateTempPasswordComponent,
VaultComponent,
VaultTimeoutInputComponent,
diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.html b/apps/desktop/src/auth/two-factor-options-v1.component.html
deleted file mode 100644
index 6f87c666215..00000000000
--- a/apps/desktop/src/auth/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
- {{ p.name }}
- {{ p.description }}
-
-
- {{ "recoveryCodeTitle" | i18n }}
- {{ "recoveryCodeDesc" | i18n }}
-
-
-
-
-
-
-
-
diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.ts b/apps/desktop/src/auth/two-factor-options-v1.component.ts
deleted file mode 100644
index 1cb440a5f5f..00000000000
--- a/apps/desktop/src/auth/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Component } from "@angular/core";
-import { Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.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";
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-}
diff --git a/apps/desktop/src/auth/two-factor-v1.component.html b/apps/desktop/src/auth/two-factor-v1.component.html
deleted file mode 100644
index 1f65d5070f6..00000000000
--- a/apps/desktop/src/auth/two-factor-v1.component.html
+++ /dev/null
@@ -1,175 +0,0 @@
-
-
diff --git a/apps/desktop/src/auth/two-factor-v1.component.ts b/apps/desktop/src/auth/two-factor-v1.component.ts
deleted file mode 100644
index 13c7d0a452b..00000000000
--- a/apps/desktop/src/auth/two-factor-v1.component.ts
+++ /dev/null
@@ -1,190 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
-import { ActivatedRoute, Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import { ModalService } from "@bitwarden/angular/services/modal.service";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
-import { ToastService } from "@bitwarden/components";
-
-import { TwoFactorOptionsComponentV1 } from "./two-factor-options-v1.component";
-
-const BroadcasterSubscriptionId = "TwoFactorComponent";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnDestroy {
- @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
- twoFactorOptionsModal: ViewContainerRef;
-
- showingModal = false;
- duoCallbackSubscriptionEnabled: boolean = false;
-
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- syncService: SyncService,
- environmentService: EnvironmentService,
- private broadcasterService: BroadcasterService,
- private modalService: ModalService,
- stateService: StateService,
- private ngZone: NgZone,
- route: ActivatedRoute,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- ssoLoginService: SsoLoginServiceAbstraction,
- configService: ConfigService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- @Inject(WINDOW) protected win: Window,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLogin = async () => {
- // 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
- syncService.fullSync(true);
- };
-
- this.onSuccessfulLoginTde = async () => {
- // 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
- syncService.fullSync(true);
- };
- }
-
- async anotherMethod() {
- const [modal, childComponent] = await this.modalService.openViewRef(
- TwoFactorOptionsComponentV1,
- this.twoFactorOptionsModal,
- );
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- modal.onShown.subscribe(() => {
- this.showingModal = true;
- });
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- modal.onClosed.subscribe(() => {
- this.showingModal = false;
- });
-
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
- childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => {
- modal.close();
- this.selectedProviderType = provider;
- await this.init();
- });
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
- childComponent.onRecoverSelected.subscribe(() => {
- modal.close();
- });
- }
-
- async submit() {
- await super.submit();
- if (this.captchaSiteKey) {
- const content = document.getElementById("content") as HTMLDivElement;
- content.setAttribute("style", "width:335px");
- }
- }
-
- protected override setupDuoResultListener() {
- if (!this.duoCallbackSubscriptionEnabled) {
- this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
- await this.ngZone.run(async () => {
- if (message.command === "duoCallback") {
- this.token = message.code + "|" + message.state;
- await this.submit();
- }
- });
- });
- this.duoCallbackSubscriptionEnabled = true;
- }
- }
-
- override async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- const duoHandOffMessage = {
- title: this.i18nService.t("youSuccessfullyLoggedIn"),
- message: this.i18nService.t("youMayCloseThisWindow"),
- isCountdown: false,
- };
-
- // we're using the connector here as a way to set a cookie with translations
- // before continuing to the duo frameless url
- const env = await firstValueFrom(this.environmentService.environment$);
- const launchUrl =
- env.getWebVaultUrl() +
- "/duo-redirect-connector.html" +
- "?duoFramelessUrl=" +
- encodeURIComponent(this.duoFramelessUrl) +
- "&handOffMessage=" +
- encodeURIComponent(JSON.stringify(duoHandOffMessage));
- this.platformUtilsService.launchUri(launchUrl);
- }
-
- ngOnDestroy(): void {
- if (this.duoCallbackSubscriptionEnabled) {
- this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
- this.duoCallbackSubscriptionEnabled = false;
- }
- }
-}
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index 07404b37fcd..42eebd98223 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -833,18 +833,6 @@
"continue": {
"message": "Continue"
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -854,18 +842,9 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -873,9 +852,6 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
@@ -3224,9 +3200,6 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredByOrgForAccount": {
"message": "Duo two-step login is required for your account."
},
diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss
index b9559e13a26..ecb36aae662 100644
--- a/apps/desktop/src/scss/pages.scss
+++ b/apps/desktop/src/scss/pages.scss
@@ -27,7 +27,6 @@
#accessibility-cookie-page,
#register-page,
#hint-page,
-#two-factor-page,
#update-temp-password-page,
#remove-password-page {
padding-top: 20px;
@@ -48,7 +47,6 @@
#accessibility-cookie-page,
#register-page,
#hint-page,
-#two-factor-page,
#lock-page,
#update-temp-password-page {
.content {
diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.html b/apps/web/src/app/auth/two-factor-options-v1.component.html
deleted file mode 100644
index 43c054060ea..00000000000
--- a/apps/web/src/app/auth/two-factor-options-v1.component.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
- {{ "twoStepOptions" | i18n }}
-
-
-
-
-
-
-
{{ p.name }}
-
{{ p.description }}
-
-
-
- {{ "select" | i18n }}
-
-
-
-
-
-
-
-
-
-
{{ "recoveryCodeTitle" | i18n }}
-
{{ "recoveryCodeDesc" | i18n }}
-
-
-
- {{ "select" | i18n }}
-
-
-
-
-
-
-
- {{ "close" | i18n }}
-
-
-
diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.ts b/apps/web/src/app/auth/two-factor-options-v1.component.ts
deleted file mode 100644
index 08665dcfcdd..00000000000
--- a/apps/web/src/app/auth/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { DialogRef } from "@angular/cdk/dialog";
-import { Component } from "@angular/core";
-import { Router } from "@angular/router";
-
-import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-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 { DialogService } from "@bitwarden/components";
-
-export enum TwoFactorOptionsDialogResult {
- Provider = "Provider selected",
- Recover = "Recover selected",
-}
-
-export type TwoFactorOptionsDialogResultType = {
- result: TwoFactorOptionsDialogResult;
- type: TwoFactorProviderType;
-};
-
-@Component({
- selector: "app-two-factor-options",
- templateUrl: "two-factor-options-v1.component.html",
-})
-export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 {
- constructor(
- twoFactorService: TwoFactorService,
- router: Router,
- i18nService: I18nService,
- platformUtilsService: PlatformUtilsService,
- environmentService: EnvironmentService,
- private dialogRef: DialogRef,
- ) {
- super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService);
- }
-
- async choose(p: any) {
- await super.choose(p);
- this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Provider, type: p.type });
- }
-
- async recover() {
- await super.recover();
- this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Recover });
- }
-
- static open(dialogService: DialogService) {
- return dialogService.open(TwoFactorOptionsComponentV1);
- }
-}
diff --git a/apps/web/src/app/auth/two-factor-v1.component.html b/apps/web/src/app/auth/two-factor-v1.component.html
deleted file mode 100644
index b78747e04c2..00000000000
--- a/apps/web/src/app/auth/two-factor-v1.component.html
+++ /dev/null
@@ -1,106 +0,0 @@
-
diff --git a/apps/web/src/app/auth/two-factor-v1.component.ts b/apps/web/src/app/auth/two-factor-v1.component.ts
deleted file mode 100644
index 9a9fab02de3..00000000000
--- a/apps/web/src/app/auth/two-factor-v1.component.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
-import { FormBuilder, Validators } from "@angular/forms";
-import { ActivatedRoute, Router } from "@angular/router";
-import { Subject, takeUntil, lastValueFrom } from "rxjs";
-
-import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { DialogService, ToastService } from "@bitwarden/components";
-
-import {
- TwoFactorOptionsDialogResult,
- TwoFactorOptionsComponentV1,
- TwoFactorOptionsDialogResultType,
-} from "./two-factor-options-v1.component";
-
-@Component({
- selector: "app-two-factor",
- templateUrl: "two-factor-v1.component.html",
-})
-export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy {
- @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
- twoFactorOptionsModal: ViewContainerRef;
- formGroup = this.formBuilder.group({
- token: [
- "",
- {
- validators: [Validators.required],
- updateOn: "submit",
- },
- ],
- remember: [false],
- });
- private destroy$ = new Subject();
- constructor(
- loginStrategyService: LoginStrategyServiceAbstraction,
- router: Router,
- i18nService: I18nService,
- apiService: ApiService,
- platformUtilsService: PlatformUtilsService,
- stateService: StateService,
- environmentService: EnvironmentService,
- private dialogService: DialogService,
- route: ActivatedRoute,
- logService: LogService,
- twoFactorService: TwoFactorService,
- appIdService: AppIdService,
- loginEmailService: LoginEmailServiceAbstraction,
- userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- ssoLoginService: SsoLoginServiceAbstraction,
- configService: ConfigService,
- masterPasswordService: InternalMasterPasswordServiceAbstraction,
- accountService: AccountService,
- toastService: ToastService,
- private formBuilder: FormBuilder,
- @Inject(WINDOW) protected win: Window,
- ) {
- super(
- loginStrategyService,
- router,
- i18nService,
- apiService,
- platformUtilsService,
- win,
- environmentService,
- stateService,
- route,
- logService,
- twoFactorService,
- appIdService,
- loginEmailService,
- userDecryptionOptionsService,
- ssoLoginService,
- configService,
- masterPasswordService,
- accountService,
- toastService,
- );
- this.onSuccessfulLoginNavigate = this.goAfterLogIn;
- }
- async ngOnInit() {
- await super.ngOnInit();
- this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
- this.token = value.token;
- this.remember = value.remember;
- });
- }
- submitForm = async () => {
- await this.submit();
- };
-
- async anotherMethod() {
- const dialogRef = TwoFactorOptionsComponentV1.open(this.dialogService);
- const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed);
- if (response.result === TwoFactorOptionsDialogResult.Provider) {
- this.selectedProviderType = response.type;
- await this.init();
- }
- }
-
- protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
- if (!result.requiresEncryptionKeyMigration) {
- return false;
- }
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate(["migrate-legacy-encryption"]);
- return true;
- }
-
- goAfterLogIn = async () => {
- this.loginEmailService.clearValues();
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.successRoute], {
- queryParams: {
- identifier: this.orgIdentifier,
- },
- });
- };
-
- private duoResultChannel: BroadcastChannel;
-
- protected override setupDuoResultListener() {
- if (!this.duoResultChannel) {
- this.duoResultChannel = new BroadcastChannel("duoResult");
- this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage);
- }
- }
-
- private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => {
- this.token = msg.data.code + "|" + msg.data.state;
- await this.submit();
- };
-
- async ngOnDestroy() {
- super.ngOnDestroy();
-
- if (this.duoResultChannel) {
- // clean up duo listener if it was initialized.
- this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage);
- this.duoResultChannel.close();
- }
- }
-}
diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts
index c531f358b34..0334519516a 100644
--- a/apps/web/src/app/oss-routing.module.ts
+++ b/apps/web/src/app/oss-routing.module.ts
@@ -2,7 +2,6 @@ import { NgModule } from "@angular/core";
import { Route, RouterModule, Routes } from "@angular/router";
import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component";
-import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap";
import {
authGuard,
lockGuard,
@@ -65,7 +64,6 @@ import { AccountComponent } from "./auth/settings/account/account.component";
import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component";
import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component";
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
-import { TwoFactorComponentV1 } from "./auth/two-factor-v1.component";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component";
@@ -378,51 +376,28 @@ const routes: Routes = [
},
],
},
- ...unauthUiRefreshSwap(
- TwoFactorComponentV1,
- TwoFactorAuthComponent,
- {
- path: "2fa",
- canActivate: [unauthGuardFn()],
- children: [
- {
- path: "",
- component: TwoFactorComponentV1,
- },
- {
- path: "",
- component: EnvironmentSelectorComponent,
- outlet: "environment-selector",
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- {
- path: "2fa",
- canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
- children: [
- {
- path: "",
- component: TwoFactorAuthComponent,
- },
- {
- path: "",
- component: EnvironmentSelectorComponent,
- outlet: "environment-selector",
- },
- ],
- data: {
- pageTitle: {
- key: "verifyYourIdentity",
- },
- titleAreaMaxWidth: "md",
- } satisfies RouteDataProperties & AnonLayoutWrapperData,
- },
- ),
+ {
+ path: "2fa",
+ component: TwoFactorAuthComponent,
+ canActivate: [unauthGuardFn(), TwoFactorAuthGuard],
+ children: [
+ {
+ path: "",
+ component: TwoFactorAuthComponent,
+ },
+ {
+ path: "",
+ component: EnvironmentSelectorComponent,
+ outlet: "environment-selector",
+ },
+ ],
+ data: {
+ pageTitle: {
+ key: "verifyYourIdentity",
+ },
+ titleAreaMaxWidth: "md",
+ } satisfies RouteDataProperties & AnonLayoutWrapperData,
+ },
{
path: "lock",
canActivate: [deepLinkGuard(), lockGuard()],
diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts
index f21a9338491..70dbf63f1f8 100644
--- a/apps/web/src/app/shared/loose-components.module.ts
+++ b/apps/web/src/app/shared/loose-components.module.ts
@@ -43,8 +43,6 @@ import { TwoFactorSetupComponent } from "../auth/settings/two-factor/two-factor-
import { TwoFactorVerifyComponent } from "../auth/settings/two-factor/two-factor-verify.component";
import { UserVerificationModule } from "../auth/shared/components/user-verification";
import { SsoComponentV1 } from "../auth/sso-v1.component";
-import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.component";
-import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component";
import { UpdatePasswordComponent } from "../auth/update-password.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component";
@@ -148,12 +146,10 @@ import { SharedModule } from "./shared.module";
SetPasswordComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
- TwoFactorComponentV1,
SsoComponentV1,
TwoFactorSetupAuthenticatorComponent,
TwoFactorSetupDuoComponent,
TwoFactorSetupEmailComponent,
- TwoFactorOptionsComponentV1,
TwoFactorRecoveryComponent,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
@@ -210,12 +206,10 @@ import { SharedModule } from "./shared.module";
SetPasswordComponent,
SponsoredFamiliesComponent,
SponsoringOrgRowComponent,
- TwoFactorComponentV1,
SsoComponentV1,
TwoFactorSetupAuthenticatorComponent,
TwoFactorSetupDuoComponent,
TwoFactorSetupEmailComponent,
- TwoFactorOptionsComponentV1,
TwoFactorSetupComponent,
TwoFactorVerifyComponent,
TwoFactorSetupWebAuthnComponent,
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b9ead1df44d..4133062a047 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -1456,18 +1456,6 @@
}
}
},
- "enterVerificationCodeApp": {
- "message": "Enter the 6 digit verification code from your authenticator app."
- },
- "enterVerificationCodeEmail": {
- "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.",
- "placeholders": {
- "email": {
- "content": "$1",
- "example": "example@gmail.com"
- }
- }
- },
"verificationCodeEmailSent": {
"message": "Verification email sent to $EMAIL$.",
"placeholders": {
@@ -1477,18 +1465,10 @@
}
}
},
- "rememberMe": {
- "message": "Remember me"
- },
+
"dontAskAgainOnThisDeviceFor30Days": {
"message": "Don't ask again on this device for 30 days"
},
- "sendVerificationCodeEmailAgain": {
- "message": "Send verification code email again"
- },
- "useAnotherTwoStepMethod": {
- "message": "Use another two-step login method"
- },
"selectAnotherMethod": {
"message": "Select another method",
"description": "Select another two-step login method"
@@ -1496,9 +1476,6 @@
"useYourRecoveryCode": {
"message": "Use your recovery code"
},
- "insertYubiKey": {
- "message": "Insert your YubiKey into your computer's USB port, then touch its button."
- },
"insertU2f": {
"message": "Insert your security key into your computer's USB port. If it has a button, touch it."
},
@@ -7273,9 +7250,6 @@
"duoHealthCheckResultsInNullAuthUrlError": {
"message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance."
},
- "launchDuoAndFollowStepsToFinishLoggingIn": {
- "message": "Launch Duo and follow the steps to finish logging in."
- },
"duoRequiredByOrgForAccount": {
"message": "Duo two-step login is required for your account."
},
diff --git a/libs/angular/src/auth/components/two-factor-options-v1.component.ts b/libs/angular/src/auth/components/two-factor-options-v1.component.ts
deleted file mode 100644
index f02eabcc156..00000000000
--- a/libs/angular/src/auth/components/two-factor-options-v1.component.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
-import { Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-
-import {
- TwoFactorProviderDetails,
- TwoFactorService,
-} from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
-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";
-
-@Directive()
-export class TwoFactorOptionsComponentV1 implements OnInit {
- @Output() onProviderSelected = new EventEmitter();
- @Output() onRecoverSelected = new EventEmitter();
-
- providers: any[] = [];
-
- constructor(
- protected twoFactorService: TwoFactorService,
- protected router: Router,
- protected i18nService: I18nService,
- protected platformUtilsService: PlatformUtilsService,
- protected win: Window,
- protected environmentService: EnvironmentService,
- ) {}
-
- async ngOnInit() {
- this.providers = await this.twoFactorService.getSupportedProviders(this.win);
- }
-
- async choose(p: TwoFactorProviderDetails) {
- this.onProviderSelected.emit(p.type);
- }
-
- async recover() {
- const env = await firstValueFrom(this.environmentService.environment$);
- const webVault = env.getWebVaultUrl();
- this.platformUtilsService.launchUri(webVault + "/#/recover-2fa");
- this.onRecoverSelected.emit();
- }
-}
diff --git a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts
deleted file mode 100644
index 47075acc758..00000000000
--- a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts
+++ /dev/null
@@ -1,505 +0,0 @@
-import { Component } from "@angular/core";
-import { ComponentFixture, TestBed } from "@angular/core/testing";
-import { ActivatedRoute, convertToParamMap, Router } from "@angular/router";
-import { mock, MockProxy } from "jest-mock-extended";
-import { BehaviorSubject } from "rxjs";
-
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
- FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
- FakeUserDecryptionOptions as UserDecryptionOptions,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec";
-import { UserId } from "@bitwarden/common/types/guid";
-import { ToastService } from "@bitwarden/components";
-
-import { TwoFactorComponentV1 } from "./two-factor-v1.component";
-
-// test component that extends the TwoFactorComponent
-@Component({})
-class TestTwoFactorComponent extends TwoFactorComponentV1 {}
-
-interface TwoFactorComponentProtected {
- trustedDeviceEncRoute: string;
- changePasswordRoute: string;
- forcePasswordResetRoute: string;
- successRoute: string;
-}
-
-describe("TwoFactorComponent", () => {
- let component: TestTwoFactorComponent;
- let _component: TwoFactorComponentProtected;
-
- let fixture: ComponentFixture;
- const userId = "userId" as UserId;
-
- // Mock Services
- let mockLoginStrategyService: MockProxy;
- let mockRouter: MockProxy;
- let mockI18nService: MockProxy;
- let mockApiService: MockProxy;
- let mockPlatformUtilsService: MockProxy;
- let mockWin: MockProxy;
- let mockEnvironmentService: MockProxy;
- let mockStateService: MockProxy;
- let mockLogService: MockProxy;
- let mockTwoFactorService: MockProxy;
- let mockAppIdService: MockProxy;
- let mockLoginEmailService: MockProxy;
- let mockUserDecryptionOptionsService: MockProxy;
- let mockSsoLoginService: MockProxy;
- let mockConfigService: MockProxy;
- let mockMasterPasswordService: FakeMasterPasswordService;
- let mockAccountService: FakeAccountService;
- let mockToastService: MockProxy;
-
- let mockUserDecryptionOpts: {
- noMasterPassword: UserDecryptionOptions;
- withMasterPassword: UserDecryptionOptions;
- withMasterPasswordAndTrustedDevice: UserDecryptionOptions;
- withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
- withMasterPasswordAndKeyConnector: UserDecryptionOptions;
- noMasterPasswordWithTrustedDevice: UserDecryptionOptions;
- noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions;
- noMasterPasswordWithKeyConnector: UserDecryptionOptions;
- };
-
- let selectedUserDecryptionOptions: BehaviorSubject;
- let authenticationSessionTimeoutSubject: BehaviorSubject;
-
- beforeEach(() => {
- authenticationSessionTimeoutSubject = new BehaviorSubject(false);
- mockLoginStrategyService = mock();
- mockLoginStrategyService.authenticationSessionTimeout$ = authenticationSessionTimeoutSubject;
- mockRouter = mock();
- mockI18nService = mock();
- mockApiService = mock();
- mockPlatformUtilsService = mock();
- mockWin = mock();
- mockEnvironmentService = mock();
- mockStateService = mock();
- mockLogService = mock();
- mockTwoFactorService = mock();
- mockAppIdService = mock();
- mockLoginEmailService = mock();
- mockUserDecryptionOptionsService = mock();
- mockSsoLoginService = mock();
- mockConfigService = mock();
- mockAccountService = mockAccountServiceWith(userId);
- mockToastService = mock();
- mockMasterPasswordService = new FakeMasterPasswordService();
-
- mockUserDecryptionOpts = {
- noMasterPassword: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: undefined,
- keyConnectorOption: undefined,
- }),
- withMasterPassword: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: undefined,
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
- keyConnectorOption: undefined,
- }),
- withMasterPasswordAndKeyConnector: new UserDecryptionOptions({
- hasMasterPassword: true,
- trustedDeviceOption: undefined,
- keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
- }),
- noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false),
- keyConnectorOption: undefined,
- }),
- noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false),
- keyConnectorOption: undefined,
- }),
- noMasterPasswordWithKeyConnector: new UserDecryptionOptions({
- hasMasterPassword: false,
- trustedDeviceOption: undefined,
- keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"),
- }),
- };
-
- selectedUserDecryptionOptions = new BehaviorSubject(
- mockUserDecryptionOpts.withMasterPassword,
- );
- mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions;
-
- TestBed.configureTestingModule({
- declarations: [TestTwoFactorComponent],
- providers: [
- { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService },
- { provide: Router, useValue: mockRouter },
- { provide: I18nService, useValue: mockI18nService },
- { provide: ApiService, useValue: mockApiService },
- { provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
- { provide: WINDOW, useValue: mockWin },
- { provide: EnvironmentService, useValue: mockEnvironmentService },
- { provide: StateService, useValue: mockStateService },
- {
- provide: ActivatedRoute,
- useValue: {
- snapshot: {
- // Default to standard 2FA flow - not SSO + 2FA
- queryParamMap: convertToParamMap({ sso: "false" }),
- },
- },
- },
- { provide: LogService, useValue: mockLogService },
- { provide: TwoFactorService, useValue: mockTwoFactorService },
- { provide: AppIdService, useValue: mockAppIdService },
- { provide: LoginEmailServiceAbstraction, useValue: mockLoginEmailService },
- {
- provide: UserDecryptionOptionsServiceAbstraction,
- useValue: mockUserDecryptionOptionsService,
- },
- { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService },
- { provide: ConfigService, useValue: mockConfigService },
- { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService },
- { provide: AccountService, useValue: mockAccountService },
- { provide: ToastService, useValue: mockToastService },
- ],
- });
-
- fixture = TestBed.createComponent(TestTwoFactorComponent);
- component = fixture.componentInstance;
- _component = component as any;
- });
-
- afterEach(() => {
- // Reset all mocks after each test
- jest.resetAllMocks();
- });
-
- it("should create", () => {
- expect(component).toBeTruthy();
- });
-
- // Shared tests
- const testChangePasswordOnSuccessfulLogin = () => {
- it("navigates to the component's defined change password route when user doesn't have a MP and key connector isn't enabled", async () => {
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.changePasswordRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- };
-
- const testForceResetOnSuccessfulLogin = (reasonString: string) => {
- it(`navigates to the component's defined forcePasswordResetRoute route when response.forcePasswordReset is ${reasonString}`, async () => {
- // Act
- await component.doSubmit();
-
- // expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.forcePasswordResetRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- };
-
- describe("Standard 2FA scenarios", () => {
- describe("doSubmit", () => {
- const token = "testToken";
- const remember = false;
- const captchaToken = "testCaptchaToken";
-
- beforeEach(() => {
- component.token = token;
- component.remember = remember;
- component.captchaToken = captchaToken;
-
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
- });
-
- it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => {
- // Arrange
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith(
- new TokenTwoFactorRequest(component.selectedProviderType, token, remember),
- captchaToken,
- );
- });
-
- it("should return when handleCaptchaRequired returns true", async () => {
- // Arrange
- const captchaSiteKey = "testCaptchaSiteKey";
- const authResult = new AuthResult();
- authResult.captchaSiteKey = captchaSiteKey;
-
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
-
- // Note: the any casts are required b/c typescript cant recognize that
- // handleCaptureRequired is a method on TwoFactorComponent b/c it is inherited
- // from the CaptchaProtectedComponent
- const handleCaptchaRequiredSpy = jest
- .spyOn(component, "handleCaptchaRequired")
- .mockReturnValue(true);
-
- // Act
- const result = await component.doSubmit();
-
- // Assert
- expect(handleCaptchaRequiredSpy).toHaveBeenCalled();
- expect(result).toBeUndefined();
- });
-
- it("calls onSuccessfulLogin when defined", async () => {
- // Arrange
- component.onSuccessfulLogin = jest.fn().mockResolvedValue(undefined);
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLogin).toHaveBeenCalled();
- });
-
- it("calls loginEmailService.clearValues() when login is successful", async () => {
- // Arrange
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
- // spy on loginEmailService.clearValues
- const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues");
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(clearValuesSpy).toHaveBeenCalled();
- });
-
- describe("Set Master Password scenarios", () => {
- beforeEach(() => {
- const authResult = new AuthResult();
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- describe("Given user needs to set a master password", () => {
- beforeEach(() => {
- // Only need to test the case where the user has no master password to test the primary change mp flow here
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword);
- });
-
- testChangePasswordOnSuccessfulLogin();
- });
-
- it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.noMasterPasswordWithKeyConnector,
- );
-
- await component.doSubmit();
-
- expect(mockRouter.navigate).not.toHaveBeenCalledWith([_component.changePasswordRoute], {
- queryParams: {
- identifier: component.orgIdentifier,
- },
- });
- });
- });
-
- describe("Force Master Password Reset scenarios", () => {
- [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ].forEach((forceResetPasswordReason) => {
- const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
-
- beforeEach(() => {
- // use standard user with MP because this test is not concerned with password reset.
- selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword);
-
- const authResult = new AuthResult();
- authResult.forcePasswordReset = forceResetPasswordReason;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- testForceResetOnSuccessfulLogin(reasonString);
- });
- });
-
- it("calls onSuccessfulLoginNavigate when the callback is defined", async () => {
- // Arrange
- component.onSuccessfulLoginNavigate = jest.fn().mockResolvedValue(undefined);
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLoginNavigate).toHaveBeenCalled();
- });
-
- it("navigates to the component's defined success route when the login is successful and onSuccessfulLoginNavigate is undefined", async () => {
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
-
- // Act
- await component.doSubmit();
-
- // Assert
- expect(component.onSuccessfulLoginNavigate).not.toBeDefined();
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith([_component.successRoute], undefined);
- });
- });
- });
-
- describe("SSO > 2FA scenarios", () => {
- beforeEach(() => {
- const mockActivatedRoute = TestBed.inject(ActivatedRoute);
- mockActivatedRoute.snapshot.queryParamMap.get = jest.fn().mockReturnValue("true");
- });
-
- describe("doSubmit", () => {
- const token = "testToken";
- const remember = false;
- const captchaToken = "testCaptchaToken";
-
- beforeEach(() => {
- component.token = token;
- component.remember = remember;
- component.captchaToken = captchaToken;
- });
-
- describe("Trusted Device Encryption scenarios", () => {
- beforeEach(() => {
- mockConfigService.getFeatureFlag.mockResolvedValue(true);
- });
-
- describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => {
- beforeEach(() => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword,
- );
-
- const authResult = new AuthResult();
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- it("navigates to the component's defined trusted device encryption route and sets correct flag when user doesn't have a MP and key connector isn't enabled", async () => {
- // Act
- await component.doSubmit();
-
- // Assert
- expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith(
- ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
- userId,
- );
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith(
- [_component.trustedDeviceEncRoute],
- undefined,
- );
- });
- });
-
- describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is required", () => {
- [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ].forEach((forceResetPasswordReason) => {
- const reasonString = ForceSetPasswordReason[forceResetPasswordReason];
-
- beforeEach(() => {
- // use standard user with MP because this test is not concerned with password reset.
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
- );
-
- const authResult = new AuthResult();
- authResult.forcePasswordReset = forceResetPasswordReason;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- testForceResetOnSuccessfulLogin(reasonString);
- });
- });
-
- describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => {
- let authResult;
- beforeEach(() => {
- selectedUserDecryptionOptions.next(
- mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice,
- );
-
- authResult = new AuthResult();
- authResult.forcePasswordReset = ForceSetPasswordReason.None;
- mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult);
- });
-
- it("navigates to the component's defined trusted device encryption route when login is successful and onSuccessfulLoginTdeNavigate is undefined", async () => {
- await component.doSubmit();
-
- expect(mockRouter.navigate).toHaveBeenCalledTimes(1);
- expect(mockRouter.navigate).toHaveBeenCalledWith(
- [_component.trustedDeviceEncRoute],
- undefined,
- );
- });
-
- it("calls onSuccessfulLoginTdeNavigate instead of router.navigate when the callback is defined", async () => {
- component.onSuccessfulLoginTdeNavigate = jest.fn().mockResolvedValue(undefined);
-
- await component.doSubmit();
-
- expect(mockRouter.navigate).not.toHaveBeenCalled();
- expect(component.onSuccessfulLoginTdeNavigate).toHaveBeenCalled();
- });
- });
- });
- });
- });
-
- it("navigates to the timeout route when timeout expires", async () => {
- authenticationSessionTimeoutSubject.next(true);
-
- expect(mockRouter.navigate).toHaveBeenCalledWith(["authentication-timeout"]);
- });
-});
diff --git a/libs/angular/src/auth/components/two-factor-v1.component.ts b/libs/angular/src/auth/components/two-factor-v1.component.ts
deleted file mode 100644
index 3fda2685f5e..00000000000
--- a/libs/angular/src/auth/components/two-factor-v1.component.ts
+++ /dev/null
@@ -1,514 +0,0 @@
-// FIXME: Update this file to be type safe and remove this and next line
-// @ts-strict-ignore
-import { Directive, Inject, OnInit, OnDestroy } from "@angular/core";
-import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
-import { ActivatedRoute, NavigationExtras, Router } from "@angular/router";
-import { firstValueFrom } from "rxjs";
-import { first } from "rxjs/operators";
-
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
-import {
- LoginStrategyServiceAbstraction,
- LoginEmailServiceAbstraction,
- TrustedDeviceUserDecryptionOption,
- UserDecryptionOptions,
- UserDecryptionOptionsServiceAbstraction,
-} from "@bitwarden/auth/common";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
-import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
-import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
-import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
-import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
-import { 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 { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
-import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
-import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
-import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
-import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
-import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
-import { ToastService } from "@bitwarden/components";
-
-import { CaptchaProtectedComponent } from "./captcha-protected.component";
-
-@Directive()
-export class TwoFactorComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy {
- token = "";
- remember = false;
- webAuthnReady = false;
- webAuthnNewTab = false;
- providers = TwoFactorProviders;
- providerType = TwoFactorProviderType;
- selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
- webAuthnSupported = false;
- webAuthn: WebAuthnIFrame = null;
- title = "";
- twoFactorEmail: string = null;
- formPromise: Promise;
- emailPromise: Promise;
- orgIdentifier: string = null;
-
- duoFramelessUrl: string = null;
- duoResultListenerInitialized = false;
-
- onSuccessfulLogin: () => Promise;
- onSuccessfulLoginNavigate: () => Promise;
-
- onSuccessfulLoginTde: () => Promise;
- onSuccessfulLoginTdeNavigate: () => Promise;
-
- protected loginRoute = "login";
-
- protected trustedDeviceEncRoute = "login-initiated";
- protected changePasswordRoute = "set-password";
- protected forcePasswordResetRoute = "update-temp-password";
- protected successRoute = "vault";
- protected twoFactorTimeoutRoute = "authentication-timeout";
-
- get isDuoProvider(): boolean {
- return (
- this.selectedProviderType === TwoFactorProviderType.Duo ||
- this.selectedProviderType === TwoFactorProviderType.OrganizationDuo
- );
- }
-
- constructor(
- protected loginStrategyService: LoginStrategyServiceAbstraction,
- protected router: Router,
- protected i18nService: I18nService,
- protected apiService: ApiService,
- protected platformUtilsService: PlatformUtilsService,
- @Inject(WINDOW) protected win: Window,
- protected environmentService: EnvironmentService,
- protected stateService: StateService,
- protected route: ActivatedRoute,
- protected logService: LogService,
- protected twoFactorService: TwoFactorService,
- protected appIdService: AppIdService,
- protected loginEmailService: LoginEmailServiceAbstraction,
- protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
- protected ssoLoginService: SsoLoginServiceAbstraction,
- protected configService: ConfigService,
- protected masterPasswordService: InternalMasterPasswordServiceAbstraction,
- protected accountService: AccountService,
- protected toastService: ToastService,
- ) {
- super(environmentService, i18nService, platformUtilsService, toastService);
-
- this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
-
- // Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired
- this.loginStrategyService.authenticationSessionTimeout$
- .pipe(takeUntilDestroyed())
- .subscribe(async (expired) => {
- if (!expired) {
- return;
- }
-
- try {
- await this.router.navigate([this.twoFactorTimeoutRoute]);
- } catch (err) {
- this.logService.error(`Failed to navigate to ${this.twoFactorTimeoutRoute} route`, err);
- }
- });
- }
-
- async ngOnInit() {
- if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.loginRoute]);
- return;
- }
-
- this.route.queryParams.pipe(first()).subscribe((qParams) => {
- if (qParams.identifier != null) {
- this.orgIdentifier = qParams.identifier;
- }
- });
-
- if (await this.needsLock()) {
- this.successRoute = "lock";
- }
-
- if (this.win != null && this.webAuthnSupported) {
- const env = await firstValueFrom(this.environmentService.environment$);
- const webVaultUrl = env.getWebVaultUrl();
- this.webAuthn = new WebAuthnIFrame(
- this.win,
- webVaultUrl,
- this.webAuthnNewTab,
- this.platformUtilsService,
- this.i18nService,
- (token: string) => {
- this.token = token;
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.submit();
- },
- (error: string) => {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("webauthnCancelOrTimeout"),
- });
- },
- (info: string) => {
- if (info === "ready") {
- this.webAuthnReady = true;
- }
- },
- );
- }
-
- this.selectedProviderType = await this.twoFactorService.getDefaultProvider(
- this.webAuthnSupported,
- );
- await this.init();
- }
-
- ngOnDestroy(): void {
- this.cleanupWebAuthn();
- this.webAuthn = null;
- }
-
- async init() {
- if (this.selectedProviderType == null) {
- this.title = this.i18nService.t("loginUnavailable");
- return;
- }
-
- this.cleanupWebAuthn();
- this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
- const providerData = await this.twoFactorService.getProviders().then((providers) => {
- return providers.get(this.selectedProviderType);
- });
- switch (this.selectedProviderType) {
- case TwoFactorProviderType.WebAuthn:
- if (!this.webAuthnNewTab) {
- setTimeout(async () => {
- await this.authWebAuthn();
- }, 500);
- }
- break;
- case TwoFactorProviderType.Duo:
- case TwoFactorProviderType.OrganizationDuo:
- // Setup listener for duo-redirect.ts connector to send back the code
- if (!this.duoResultListenerInitialized) {
- // setup client specific duo result listener
- this.setupDuoResultListener();
- this.duoResultListenerInitialized = true;
- }
- // flow must be launched by user so they can choose to remember the device or not.
- this.duoFramelessUrl = providerData.AuthUrl;
- break;
- case TwoFactorProviderType.Email:
- this.twoFactorEmail = providerData.Email;
- if ((await this.twoFactorService.getProviders()).size > 1) {
- await this.sendEmail(false);
- }
- break;
- default:
- break;
- }
- }
-
- async submit() {
- await this.setupCaptcha();
-
- if (this.token == null || this.token === "") {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("verificationCodeRequired"),
- });
- return;
- }
-
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
- if (this.webAuthn != null) {
- this.webAuthn.stop();
- } else {
- return;
- }
- } else if (
- this.selectedProviderType === TwoFactorProviderType.Email ||
- this.selectedProviderType === TwoFactorProviderType.Authenticator
- ) {
- this.token = this.token.replace(" ", "").trim();
- }
-
- await this.doSubmit();
- if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
- this.webAuthn.start();
- }
- }
-
- async doSubmit() {
- this.formPromise = this.loginStrategyService.logInTwoFactor(
- new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember),
- this.captchaToken,
- );
- const authResult: AuthResult = await this.formPromise;
-
- await this.handleLoginResponse(authResult);
- }
-
- protected handleMigrateEncryptionKey(result: AuthResult): boolean {
- if (!result.requiresEncryptionKeyMigration) {
- return false;
- }
-
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccured"),
- message: this.i18nService.t("encryptionKeyMigrationRequired"),
- });
- return true;
- }
-
- // Each client will have own implementation
- protected setupDuoResultListener(): void {}
-
- private async handleLoginResponse(authResult: AuthResult) {
- if (this.handleCaptchaRequired(authResult)) {
- return;
- } else if (this.handleMigrateEncryptionKey(authResult)) {
- return;
- }
-
- // Save off the OrgSsoIdentifier for use in the TDE flows
- // - TDE login decryption options component
- // - Browser SSO on extension open
- const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
- await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId);
- this.loginEmailService.clearValues();
-
- // note: this flow affects both TDE & standard users
- if (this.isForcePasswordResetRequired(authResult)) {
- return await this.handleForcePasswordReset(this.orgIdentifier);
- }
-
- const userDecryptionOpts = await firstValueFrom(
- this.userDecryptionOptionsService.userDecryptionOptions$,
- );
-
- const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption);
-
- if (tdeEnabled) {
- return await this.handleTrustedDeviceEncryptionEnabled(
- authResult,
- this.orgIdentifier,
- userDecryptionOpts,
- );
- }
-
- // User must set password if they don't have one and they aren't using either TDE or key connector.
- const requireSetPassword =
- !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined;
-
- if (requireSetPassword || authResult.resetMasterPassword) {
- // Change implies going no password -> password in this case
- return await this.handleChangePasswordRequired(this.orgIdentifier);
- }
-
- return await this.handleSuccessfulLogin();
- }
-
- private async isTrustedDeviceEncEnabled(
- trustedDeviceOption: TrustedDeviceUserDecryptionOption,
- ): Promise {
- const ssoTo2faFlowActive = this.route.snapshot.queryParamMap.get("sso") === "true";
-
- return ssoTo2faFlowActive && trustedDeviceOption !== undefined;
- }
-
- private async handleTrustedDeviceEncryptionEnabled(
- authResult: AuthResult,
- orgIdentifier: string,
- userDecryptionOpts: UserDecryptionOptions,
- ): Promise {
- // If user doesn't have a MP, but has reset password permission, they must set a MP
- if (
- !userDecryptionOpts.hasMasterPassword &&
- userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission
- ) {
- // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device)
- // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and
- // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key.
- const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
- await this.masterPasswordService.setForceSetPasswordReason(
- ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission,
- userId,
- );
- }
-
- if (this.onSuccessfulLoginTde != null) {
- // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete
- // before navigating to the success route.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.onSuccessfulLoginTde();
- }
-
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.navigateViaCallbackOrRoute(
- this.onSuccessfulLoginTdeNavigate,
- // Navigate to TDE page (if user was on trusted device and TDE has decrypted
- // their user key, the login-initiated guard will redirect them to the vault)
- [this.trustedDeviceEncRoute],
- );
- }
-
- private async handleChangePasswordRequired(orgIdentifier: string) {
- await this.router.navigate([this.changePasswordRoute], {
- queryParams: {
- identifier: orgIdentifier,
- },
- });
- }
-
- /**
- * Determines if a user needs to reset their password based on certain conditions.
- * Users can be forced to reset their password via an admin or org policy disallowing weak passwords.
- * Note: this is different from the SSO component login flow as a user can
- * login with MP and then have to pass 2FA to finish login and we can actually
- * evaluate if they have a weak password at that time.
- *
- * @param {AuthResult} authResult - The authentication result.
- * @returns {boolean} Returns true if a password reset is required, false otherwise.
- */
- private isForcePasswordResetRequired(authResult: AuthResult): boolean {
- const forceResetReasons = [
- ForceSetPasswordReason.AdminForcePasswordReset,
- ForceSetPasswordReason.WeakMasterPassword,
- ];
-
- return forceResetReasons.includes(authResult.forcePasswordReset);
- }
-
- private async handleForcePasswordReset(orgIdentifier: string) {
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.router.navigate([this.forcePasswordResetRoute], {
- queryParams: {
- identifier: orgIdentifier,
- },
- });
- }
-
- private async handleSuccessfulLogin() {
- if (this.onSuccessfulLogin != null) {
- // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete
- // before navigating to the success route.
- // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- this.onSuccessfulLogin();
- }
- await this.navigateViaCallbackOrRoute(this.onSuccessfulLoginNavigate, [this.successRoute]);
- }
-
- private async navigateViaCallbackOrRoute(
- callback: () => Promise,
- commands: unknown[],
- extras?: NavigationExtras,
- ): Promise {
- if (callback) {
- await callback();
- } else {
- await this.router.navigate(commands, extras);
- }
- }
-
- async sendEmail(doToast: boolean) {
- if (this.selectedProviderType !== TwoFactorProviderType.Email) {
- return;
- }
-
- if (this.emailPromise != null) {
- return;
- }
-
- if ((await this.loginStrategyService.getEmail()) == null) {
- this.toastService.showToast({
- variant: "error",
- title: this.i18nService.t("errorOccurred"),
- message: this.i18nService.t("sessionTimeout"),
- });
- return;
- }
-
- try {
- const request = new TwoFactorEmailRequest();
- request.email = await this.loginStrategyService.getEmail();
- request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash();
- request.ssoEmail2FaSessionToken =
- await this.loginStrategyService.getSsoEmail2FaSessionToken();
- request.deviceIdentifier = await this.appIdService.getAppId();
- request.authRequestAccessCode = await this.loginStrategyService.getAccessCode();
- request.authRequestId = await this.loginStrategyService.getAuthRequestId();
- this.emailPromise = this.apiService.postTwoFactorEmail(request);
- await this.emailPromise;
- if (doToast) {
- this.toastService.showToast({
- variant: "success",
- title: null,
- message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail),
- });
- }
- } catch (e) {
- this.logService.error(e);
- }
-
- this.emailPromise = null;
- }
-
- async authWebAuthn() {
- const providerData = await this.twoFactorService.getProviders().then((providers) => {
- return providers.get(this.selectedProviderType);
- });
-
- if (!this.webAuthnSupported || this.webAuthn == null) {
- return;
- }
-
- this.webAuthn.init(providerData);
- }
-
- private cleanupWebAuthn() {
- if (this.webAuthn != null) {
- this.webAuthn.stop();
- this.webAuthn.cleanup();
- }
- }
-
- private async authing(): Promise {
- return (await firstValueFrom(this.loginStrategyService.currentAuthType$)) !== null;
- }
-
- private async needsLock(): Promise {
- const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$);
- return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey;
- }
-
- async launchDuoFrameless() {
- if (this.duoFramelessUrl === null) {
- this.toastService.showToast({
- variant: "error",
- title: null,
- message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"),
- });
- return;
- }
-
- this.platformUtilsService.launchUri(this.duoFramelessUrl);
- }
-}
diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
deleted file mode 100644
index b19e73a7412..00000000000
--- a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Type, inject } from "@angular/core";
-import { Route, Routes } from "@angular/router";
-
-import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
-import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
-
-import { componentRouteSwap } from "../../utils/component-route-swap";
-
-/**
- * Helper function to swap between two components based on the UnauthenticatedExtensionUIRefresh feature flag.
- * We need this because the auth teams's authenticated UI will be refreshed as part of the MVP but the
- * unauthenticated UIs will not necessarily make the cut.
- * Note: Even though this is primarily an extension refresh initiative, this will be used across clients
- * as we are consolidating the unauthenticated UIs into single libs/auth components which affects all clients.
- * @param defaultComponent - The current non-refreshed component to render.
- * @param refreshedComponent - The new refreshed component to render.
- * @param options - The shared route options to apply to both components.
- * @param altOptions - The alt route options to apply to the alt component. If not provided, the base options will be used.
- */
-export function unauthUiRefreshSwap(
- defaultComponent: Type,
- refreshedComponent: Type,
- options: Route,
- altOptions?: Route,
-): Routes {
- return componentRouteSwap(
- defaultComponent,
- refreshedComponent,
- async () => {
- const configService = inject(ConfigService);
- return configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh);
- },
- options,
- altOptions,
- );
-}
From f759e62aeb9e10ac0bcc7aba6e892025e6ebdb9d Mon Sep 17 00:00:00 2001
From: Addison Beck
Date: Fri, 28 Mar 2025 14:17:18 -0400
Subject: [PATCH 41/66] fix(browser): restore timer based background syncs
(#14031)
* docs: fix a typo
* fix(browser): restore timer-based background syncs
The browser extension was not performing scheduled background syncs every 30 minutes as expected. This was due to missing task scheduling code that was accidentally removed during the web push implementation (PR #11346).
This commit:
- Creates a new BackgroundSyncService to manage sync scheduling
- Properly initializes the sync interval in main.background.ts
- Adds a test to ensure the sync initialization code isn't accidentally removed again
- Organizes platform module structure to support the new service
Fixes PM-19396
* review: remove unecassary await keyword
---
.../src/background/main.background.spec.ts | 13 +++
.../browser/src/background/main.background.ts | 10 +-
apps/browser/tsconfig.json | 1 +
.../scheduling/task-scheduler.service.ts | 2 +-
.../background-sync.service.spec.ts | 107 ++++++++++++++++++
.../background-sync.service.ts | 44 +++++++
libs/platform/src/background-sync/index.ts | 1 +
libs/platform/src/index.ts | 1 +
libs/platform/tsconfig.json | 4 +-
tsconfig.json | 1 +
10 files changed, 178 insertions(+), 6 deletions(-)
create mode 100644 apps/browser/src/background/main.background.spec.ts
create mode 100644 libs/platform/src/background-sync/background-sync.service.spec.ts
create mode 100644 libs/platform/src/background-sync/background-sync.service.ts
create mode 100644 libs/platform/src/background-sync/index.ts
diff --git a/apps/browser/src/background/main.background.spec.ts b/apps/browser/src/background/main.background.spec.ts
new file mode 100644
index 00000000000..c2cd38b7a30
--- /dev/null
+++ b/apps/browser/src/background/main.background.spec.ts
@@ -0,0 +1,13 @@
+// This test skips all the initilization of the background script and just
+// focuses on making sure we don't accidently delete the initilization of
+// background vault syncing. This has happened before!
+describe("MainBackground sync task scheduling", () => {
+ it("includes code to schedule the sync interval task", () => {
+ // Get the bootstrap method source code as string
+ const { default: MainBackground } = jest.requireActual("./main.background");
+ const bootstrapSource = MainBackground.prototype.bootstrap.toString();
+
+ // Check that the source includes the critical sync interval scheduling code
+ expect(bootstrapSource).toContain("this.backgroundSyncService.init();");
+ });
+});
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index e1f0b8bfc64..cae554c872c 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -127,7 +127,6 @@ import {
WebPushNotificationsApiService,
WorkerWebPushConnectionService,
} from "@bitwarden/common/platform/notifications/internal";
-import { ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
import { ConfigApiService } from "@bitwarden/common/platform/services/config/config-api.service";
import { DefaultConfigService } from "@bitwarden/common/platform/services/config/default-config.service";
@@ -222,6 +221,7 @@ import {
KdfConfigService,
KeyService as KeyServiceAbstraction,
} from "@bitwarden/key-management";
+import { BackgroundSyncService } from "@bitwarden/platform/background-sync";
import {
IndividualVaultExportService,
IndividualVaultExportServiceAbstraction,
@@ -391,6 +391,7 @@ export default class MainBackground {
offscreenDocumentService: OffscreenDocumentService;
syncServiceListener: SyncServiceListener;
browserInitialInstallService: BrowserInitialInstallService;
+ backgroundSyncService: BackgroundSyncService;
webPushConnectionService: WorkerWebPushConnectionService | UnsupportedWebPushConnectionService;
themeStateService: DefaultThemeStateService;
@@ -585,9 +586,9 @@ export default class MainBackground {
this.logService,
this.stateProvider,
);
- this.taskSchedulerService.registerTaskHandler(ScheduledTaskNames.scheduleNextSyncInterval, () =>
- this.fullSync(),
- );
+
+ this.backgroundSyncService = new BackgroundSyncService(this.taskSchedulerService);
+ this.backgroundSyncService.register(() => this.fullSync());
this.environmentService = new BrowserEnvironmentService(
this.logService,
@@ -1368,6 +1369,7 @@ export default class MainBackground {
setTimeout(async () => {
await this.refreshBadge();
await this.fullSync(false);
+ this.backgroundSyncService.init();
this.notificationsService.startListening();
resolve();
}, 500);
diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json
index e836af1327c..e24985f58af 100644
--- a/apps/browser/tsconfig.json
+++ b/apps/browser/tsconfig.json
@@ -29,6 +29,7 @@
"@bitwarden/key-management": ["../../libs/key-management/src"],
"@bitwarden/key-management-ui": ["../../libs/key-management-ui/src"],
"@bitwarden/platform": ["../../libs/platform/src"],
+ "@bitwarden/platform/*": ["../../libs/platform/src/*"],
"@bitwarden/send-ui": ["../../libs/tools/send/send-ui/src"],
"@bitwarden/tools-card": ["../../libs/tools/card/src"],
"@bitwarden/ui-common": ["../../libs/ui/common/src"],
diff --git a/libs/common/src/platform/scheduling/task-scheduler.service.ts b/libs/common/src/platform/scheduling/task-scheduler.service.ts
index 35a577dda7d..3ea80c3ee0a 100644
--- a/libs/common/src/platform/scheduling/task-scheduler.service.ts
+++ b/libs/common/src/platform/scheduling/task-scheduler.service.ts
@@ -11,7 +11,7 @@ import { ScheduledTaskName } from "./scheduled-task-name.enum";
* in the future but the task that is ran is NOT the remainder of your RXJS pipeline. The
* task you want ran must instead be registered in a location reachable on a service worker
* startup (on browser). An example of an acceptible location is the constructor of a service
- * you know is created in `MainBackground`. Uses of this API is other clients _can_ have the
+ * you know is created in `MainBackground`. Uses of this API in other clients _can_ have the
* `registerTaskHandler` call in more places, but in order to have it work across clients
* it is recommended to register it according to the rules of browser.
*
diff --git a/libs/platform/src/background-sync/background-sync.service.spec.ts b/libs/platform/src/background-sync/background-sync.service.spec.ts
new file mode 100644
index 00000000000..1deb907b151
--- /dev/null
+++ b/libs/platform/src/background-sync/background-sync.service.spec.ts
@@ -0,0 +1,107 @@
+import { mock, MockProxy } from "jest-mock-extended";
+
+import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
+
+import { BackgroundSyncService, DEFAULT_SYNC_INTERVAL_MS } from "./background-sync.service";
+
+describe("BackgroundSyncService", () => {
+ let taskSchedulerService: MockProxy;
+ let backgroundSyncService: BackgroundSyncService;
+
+ beforeEach(() => {
+ taskSchedulerService = mock();
+ backgroundSyncService = new BackgroundSyncService(taskSchedulerService);
+ });
+
+ describe("register", () => {
+ it("registers a task handler with the correct task name", () => {
+ // Arrange
+ const syncCallback = jest.fn().mockResolvedValue(undefined);
+
+ // Act
+ backgroundSyncService.register(syncCallback);
+
+ // Assert
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ });
+ });
+
+ describe("init", () => {
+ it("schedules the sync interval task with default interval", () => {
+ // Act
+ backgroundSyncService.init();
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+
+ it("schedules the sync interval task with custom interval", () => {
+ // Arrange
+ const customInterval = 60000; // 1 minute
+
+ // Act
+ backgroundSyncService.init(customInterval);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ customInterval,
+ );
+ });
+
+ it("correctly handles zero interval by using default", () => {
+ // Act
+ backgroundSyncService.init(0);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+
+ it("correctly handles negative interval by using default", () => {
+ // Act
+ backgroundSyncService.init(-1000);
+
+ // Assert
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledTimes(1);
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ DEFAULT_SYNC_INTERVAL_MS,
+ );
+ });
+ });
+
+ describe("full integration", () => {
+ it("registers and initializes correctly in sequence", () => {
+ // Arrange
+ const syncCallback = jest.fn().mockResolvedValue(undefined);
+ const customInterval = 45000; // 45 seconds
+
+ // Act
+ backgroundSyncService.register(syncCallback);
+ backgroundSyncService.init(customInterval);
+
+ // Assert
+ expect(taskSchedulerService.registerTaskHandler).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ expect(taskSchedulerService.setInterval).toHaveBeenCalledWith(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ customInterval,
+ );
+ });
+ });
+});
diff --git a/libs/platform/src/background-sync/background-sync.service.ts b/libs/platform/src/background-sync/background-sync.service.ts
new file mode 100644
index 00000000000..dc1b49d399e
--- /dev/null
+++ b/libs/platform/src/background-sync/background-sync.service.ts
@@ -0,0 +1,44 @@
+import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling";
+
+/**
+ * The default interval between background syncs.
+ * 300,000ms = 5 minutes
+ */
+export const DEFAULT_SYNC_INTERVAL_MS = 300000;
+
+/**
+ * Service responsible for registering and managing background synchronization for the browser extension.
+ * Handles scheduling of periodic sync operations using the task scheduler infrastructure.
+ */
+
+export class BackgroundSyncService {
+ /**
+ * Creates a new instance of BackgroundSyncService.
+ * @param taskSchedulerService - Service that handles scheduling and execution of periodic tasks
+ */
+ constructor(private taskSchedulerService: TaskSchedulerService) {}
+
+ /**
+ * Registers a callback function to be executed when the sync interval task is triggered.
+ * This associates the sync task name with the provided callback in the task scheduler.
+ *
+ * @param syncCallback - The function to execute when the sync task is triggered
+ */
+ register(syncCallback: () => Promise) {
+ this.taskSchedulerService.registerTaskHandler(
+ ScheduledTaskNames.scheduleNextSyncInterval,
+ syncCallback,
+ );
+ }
+
+ /**
+ * Initializes the background sync service by scheduling the sync interval task.
+ * This sets up a recurring timer that triggers the registered sync callback at regular intervals.
+ *
+ * @param intervalMs - The interval in milliseconds between sync operations (defaults to 300000ms/5 minutes)
+ */
+ init(intervalMs: number = DEFAULT_SYNC_INTERVAL_MS) {
+ intervalMs = intervalMs < 1 ? DEFAULT_SYNC_INTERVAL_MS : intervalMs;
+ this.taskSchedulerService.setInterval(ScheduledTaskNames.scheduleNextSyncInterval, intervalMs);
+ }
+}
diff --git a/libs/platform/src/background-sync/index.ts b/libs/platform/src/background-sync/index.ts
new file mode 100644
index 00000000000..adfeec608be
--- /dev/null
+++ b/libs/platform/src/background-sync/index.ts
@@ -0,0 +1 @@
+export * from "./background-sync.service";
diff --git a/libs/platform/src/index.ts b/libs/platform/src/index.ts
index f11ec102845..3fabe3fad1a 100644
--- a/libs/platform/src/index.ts
+++ b/libs/platform/src/index.ts
@@ -1 +1,2 @@
export * from "./services/browser-service";
+export * from "./background-sync";
diff --git a/libs/platform/tsconfig.json b/libs/platform/tsconfig.json
index eaa021247d8..898f9e41c6a 100644
--- a/libs/platform/tsconfig.json
+++ b/libs/platform/tsconfig.json
@@ -1,7 +1,9 @@
{
"extends": "../shared/tsconfig",
"compilerOptions": {
- "paths": {}
+ "paths": {
+ "@bitwarden/common/*": ["../common/src/*"]
+ }
},
"include": ["src", "spec"],
"exclude": ["node_modules", "dist"]
diff --git a/tsconfig.json b/tsconfig.json
index fb50f1e7033..c82851d50c8 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -35,6 +35,7 @@
"@bitwarden/key-management-ui": ["./libs/key-management-ui/src"],
"@bitwarden/node/*": ["./libs/node/src/*"],
"@bitwarden/platform": ["./libs/platform/src"],
+ "@bitwarden/platform/*": ["./libs/platform/src/*"],
"@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"],
"@bitwarden/tools-card": ["./libs/tools/card/src"],
"@bitwarden/ui-common": ["./libs/ui/common/src"],
From 66a914badfd65046ce5969163e957ef8f919f5a8 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Fri, 28 Mar 2025 15:50:30 -0400
Subject: [PATCH 42/66] [PM-19654] add hideIcon option to extension anon layout
(#14045)
---
.../extension-anon-layout-wrapper.component.html | 1 +
.../extension-anon-layout-wrapper.component.ts | 10 ++++++++++
.../extension-anon-layout-wrapper.stories.ts | 2 ++
.../anon-layout/anon-layout.component.html | 2 +-
.../angular/anon-layout/anon-layout.component.ts | 1 +
.../angular/anon-layout/anon-layout.stories.ts | 16 ++++++++++++++++
6 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
index 88a3b1c3076..54cb5203a87 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.html
@@ -21,6 +21,7 @@
[hideLogo]="true"
[maxWidth]="maxWidth"
[hideFooter]="hideFooter"
+ [hideIcon]="hideIcon"
>
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
index 10ef65d0654..51dbb6503d7 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts
@@ -26,6 +26,7 @@ export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData {
showBackButton?: boolean;
showLogo?: boolean;
hideFooter?: boolean;
+ hideIcon?: boolean;
}
@Component({
@@ -48,6 +49,7 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
protected showAcctSwitcher: boolean;
protected showBackButton: boolean;
protected showLogo: boolean = true;
+ protected hideIcon: boolean = false;
protected pageTitle: string;
protected pageSubtitle: string;
@@ -129,6 +131,10 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
if (firstChildRouteData["showLogo"] !== undefined) {
this.showLogo = Boolean(firstChildRouteData["showLogo"]);
}
+
+ if (firstChildRouteData["hideIcon"] !== undefined) {
+ this.hideIcon = Boolean(firstChildRouteData["hideIcon"]);
+ }
}
private listenForServiceDataChanges() {
@@ -180,6 +186,10 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy {
if (data.showLogo !== undefined) {
this.showLogo = data.showLogo;
}
+
+ if (data.hideIcon !== undefined) {
+ this.hideIcon = data.hideIcon;
+ }
}
private handleStringOrTranslation(value: string | Translation): string {
diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
index 841eefda0ad..a0990485d49 100644
--- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
+++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts
@@ -242,6 +242,7 @@ const initialData: ExtensionAnonLayoutWrapperData = {
showAcctSwitcher: true,
showBackButton: true,
showLogo: true,
+ hideIcon: false,
};
const changedData: ExtensionAnonLayoutWrapperData = {
@@ -255,6 +256,7 @@ const changedData: ExtensionAnonLayoutWrapperData = {
showAcctSwitcher: false,
showBackButton: false,
showLogo: false,
+ hideIcon: false,
};
@Component({
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html
index 4120ea59002..f31a5500b43 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.component.html
+++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html
@@ -17,7 +17,7 @@
class="tw-text-center tw-mb-4 sm:tw-mb-6"
[ngClass]="{ 'tw-max-w-md tw-mx-auto': titleAreaMaxWidth === 'md' }"
>
-
+
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts
index 05ddb9614f1..1ca4ccd2432 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts
+++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts
@@ -39,6 +39,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges {
@Input() showReadonlyHostname: boolean;
@Input() hideLogo: boolean = false;
@Input() hideFooter: boolean = false;
+ @Input() hideIcon: boolean = false;
/**
* Max width of the title area content
diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
index c7e15d9dcfa..34d561d5210 100644
--- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
+++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts
@@ -163,6 +163,22 @@ export const WithCustomIcon: Story = {
}),
};
+export const HideIcon: Story = {
+ render: (args) => ({
+ props: args,
+ template:
+ // Projected content (the
) and styling is just a sample and can be replaced with any content/styling.
+ `
+
+
+
Primary Projected Content Area (customizable)
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Necessitatibus illum vero, placeat recusandae esse ratione eius minima veniam nemo, quas beatae! Impedit molestiae alias sapiente explicabo. Sapiente corporis ipsa numquam?
+
+
+ `,
+ }),
+};
+
export const HideLogo: Story = {
render: (args) => ({
props: args,
From 907abc9dae3ecb5b994d9e6d852d60576ec64bdb Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Sun, 30 Mar 2025 15:49:52 -0400
Subject: [PATCH 43/66] Complete feature flag grouping by team (#14054)
* Completed feature flag grouping
* Added organization of default value section.
* Clarified comment.
---
libs/common/src/enums/feature-flag.enum.ts | 52 +++++++++++++---------
1 file changed, 31 insertions(+), 21 deletions(-)
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index c5119cd5206..1907f6539c5 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -2,6 +2,8 @@
* Feature flags.
*
* Flags MUST be short lived and SHALL be removed once enabled.
+ *
+ * Flags should be grouped by team to have visibility of ownership and cleanup.
*/
export enum FeatureFlag {
/* Admin Console Team */
@@ -9,6 +11,11 @@ export enum FeatureFlag {
VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint",
LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission",
SsoExternalIdVisibility = "pm-18630-sso-external-id-visibility",
+ AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
+
+ /* Auth */
+ PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
+ UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -21,6 +28,18 @@ export enum FeatureFlag {
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
NotificationRefresh = "notification-refresh",
UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection",
+ MacOsNativeCredentialSync = "macos-native-credential-sync",
+
+ /* Billing */
+ TrialPaymentOptional = "PM-8163-trial-payment",
+ PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
+ PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
+ PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method",
+
+ /* Key Management */
+ PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
+ UserKeyRotationV2 = "userkey-rotation-v2",
+ PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
/* Tools */
ItemShare = "item-share",
@@ -36,21 +55,7 @@ export enum FeatureFlag {
NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss",
VaultBulkManagementAction = "vault-bulk-management-action",
SecurityTasks = "security-tasks",
-
- /* Auth */
- PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
-
- UserKeyRotationV2 = "userkey-rotation-v2",
- PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
- UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
CipherKeyEncryption = "cipher-key-encryption",
- TrialPaymentOptional = "PM-8163-trial-payment",
- MacOsNativeCredentialSync = "macos-native-credential-sync",
- PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
- AccountDeprovisioningBanner = "pm-17120-account-deprovisioning-admin-console-banner",
- PM15179_AddExistingOrgsFromProviderPortal = "pm-15179-add-existing-orgs-from-provider-portal",
- PM12276_BreadcrumbEventLogs = "pm-12276-breadcrumbing-for-business-features",
- PM18794_ProviderPaymentMethod = "pm-18794-provider-payment-method",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -63,6 +68,8 @@ const FALSE = false as boolean;
*
* DO NOT enable previously disabled flags, REMOVE them instead.
* We support true as a value as we prefer flags to "enable" not "disable".
+ *
+ * Flags should be grouped by team to have visibility of ownership and cleanup.
*/
export const DefaultFeatureFlagValue = {
/* Admin Console Team */
@@ -70,6 +77,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE,
[FeatureFlag.LimitItemDeletion]: FALSE,
[FeatureFlag.SsoExternalIdVisibility]: FALSE,
+ [FeatureFlag.AccountDeprovisioningBanner]: FALSE,
/* Autofill */
[FeatureFlag.BlockBrowserInjectionsByDomain]: FALSE,
@@ -82,6 +90,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
[FeatureFlag.NotificationRefresh]: FALSE,
[FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE,
+ [FeatureFlag.MacOsNativeCredentialSync]: FALSE,
/* Tools */
[FeatureFlag.ItemShare]: FALSE,
@@ -97,21 +106,22 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE,
[FeatureFlag.VaultBulkManagementAction]: FALSE,
[FeatureFlag.SecurityTasks]: FALSE,
+ [FeatureFlag.CipherKeyEncryption]: FALSE,
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
-
- [FeatureFlag.UserKeyRotationV2]: FALSE,
- [FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
[FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
- [FeatureFlag.CipherKeyEncryption]: FALSE,
+
+ /* Billing */
[FeatureFlag.TrialPaymentOptional]: FALSE,
- [FeatureFlag.MacOsNativeCredentialSync]: FALSE,
- [FeatureFlag.PrivateKeyRegeneration]: FALSE,
- [FeatureFlag.AccountDeprovisioningBanner]: FALSE,
[FeatureFlag.PM15179_AddExistingOrgsFromProviderPortal]: FALSE,
[FeatureFlag.PM12276_BreadcrumbEventLogs]: FALSE,
[FeatureFlag.PM18794_ProviderPaymentMethod]: FALSE,
+
+ /* Key Management */
+ [FeatureFlag.PrivateKeyRegeneration]: FALSE,
+ [FeatureFlag.UserKeyRotationV2]: FALSE,
+ [FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
} satisfies Record
;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
From 56672a3568da7ef0a79de6d18f0ab6a6baab47b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?=
Date: Mon, 31 Mar 2025 12:59:47 +0200
Subject: [PATCH 44/66] [BRE-714] Enhance TestFlight desktop publishing
(#13871)
* Update TestFlight deployment to use Fastlane for app uploads
* Update TestFlight deployment to use Fastlane for app uploads
* Fix
* Fix create secret for fastlane
* Fix create secret for fastlane
* Fix create secret for fastlane
* Install gsed to use sed on macos runner
* Create test file
* Fix test
* Use actual token
* Add TestFlight distribution option for QA testing
* Update .github/workflows/build-desktop.yml
Co-authored-by: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com>
* Add if to secret construction for fastlane
---------
Co-authored-by: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com>
---
.github/workflows/build-desktop.yml | 42 ++++++++++++++++++++++++-----
1 file changed, 35 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml
index 48ecca540e8..72b60da97a1 100644
--- a/.github/workflows/build-desktop.yml
+++ b/.github/workflows/build-desktop.yml
@@ -33,6 +33,10 @@ on:
description: "Custom SDK branch"
required: false
type: string
+ testflight_distribute:
+ description: "Force distribute to TestFlight regardless of branch (useful for QA testing on feature branches)"
+ type: boolean
+ default: true
defaults:
run:
@@ -1208,21 +1212,45 @@ jobs:
path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg
if-no-files-found: error
+ - name: Create secrets for Fastlane
+ if: |
+ github.event_name != 'pull_request_target'
+ && (inputs.testflight_distribute || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
+ run: |
+ brew install gsed
+
+ KEY_WITHOUT_NEWLINES=$(gsed -E ':a;N;$!ba;s/\r{0,1}\n/\\n/g' ~/private_keys/AuthKey_6TV9MKN3GP.p8)
+
+ cat << EOF > ~/secrets/appstoreconnect-fastlane.json
+ {
+ "issuer_id": "${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}",
+ "key_id": "6TV9MKN3GP",
+ "key": "$KEY_WITHOUT_NEWLINES"
+ }
+ EOF
+
- name: Deploy to TestFlight
id: testflight-deploy
if: |
github.event_name != 'pull_request_target'
- && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
+ && (inputs.testflight_distribute || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc-desktop')
env:
APP_STORE_CONNECT_TEAM_ISSUER: ${{ secrets.APP_STORE_CONNECT_TEAM_ISSUER }}
APP_STORE_CONNECT_AUTH_KEY: 6TV9MKN3GP
+ BRANCH: ${{ github.ref }}
run: |
- xcrun altool \
- --upload-app \
- --type macos \
- --file "$(find ./dist/mas-universal/Bitwarden*.pkg)" \
- --apiKey $APP_STORE_CONNECT_AUTH_KEY \
- --apiIssuer $APP_STORE_CONNECT_TEAM_ISSUER
+
+ GIT_CHANGE="$(git show -s --format=%s)"
+
+ BRANCH=$(echo $BRANCH | sed 's/refs\/heads\///')
+
+ CHANGELOG="$BRANCH: $GIT_CHANGE"
+
+ fastlane pilot upload \
+ --app_identifier "com.bitwarden.desktop" \
+ --changelog "$CHANGELOG" \
+ --api_key_path $HOME/secrets/appstoreconnect-fastlane.json \
+ --pkg "$(find ./dist/mas-universal/Bitwarden*.pkg)"
- name: Post message to a Slack channel
id: slack-message
From 51bfbcf09056775e60fd24b11e37570e46835e85 Mon Sep 17 00:00:00 2001
From: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Date: Mon, 31 Mar 2025 09:11:47 -0400
Subject: [PATCH 45/66] chore(UI Refresh): [PM-19679] Remove unauth-ui-refresh
flag from clients
* Completed feature flag grouping
* Added organization of default value section.
* Clarified comment.
* Removed flag
* Removed merge error that duplicated comment.
---
libs/common/src/enums/feature-flag.enum.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 1907f6539c5..798053c09d0 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -15,7 +15,6 @@ export enum FeatureFlag {
/* Auth */
PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence",
- UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh",
/* Autofill */
BlockBrowserInjectionsByDomain = "block-browser-injections-by-domain",
@@ -110,7 +109,6 @@ export const DefaultFeatureFlagValue = {
/* Auth */
[FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE,
- [FeatureFlag.UnauthenticatedExtensionUIRefresh]: FALSE,
/* Billing */
[FeatureFlag.TrialPaymentOptional]: FALSE,
From 49924512b87098077440bcd702bd11ef318d19de Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:55:20 +0200
Subject: [PATCH 46/66] [PM-19656] Fix zip option not being set correctly after
navigating to Admin Console (#14058)
* Simplify if to reduce nesting
* Start subscribing to changes of the vaultSelector as soon as possible during ngOnInit
---------
Co-authored-by: Daniel James Smith
---
.../src/components/export.component.ts | 30 +++++++++----------
1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
index aecc6dcc330..c8efe093762 100644
--- a/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
+++ b/libs/tools/export/vault-export/vault-export-ui/src/components/export.component.ts
@@ -225,6 +225,20 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
),
);
+ combineLatest([
+ this.exportForm.controls.vaultSelector.valueChanges,
+ this.isExportAttachmentsEnabled$,
+ ])
+ .pipe(takeUntil(this.destroy$))
+ .subscribe(([value, isExportAttachmentsEnabled]) => {
+ this.organizationId = value !== "myVault" ? value : undefined;
+
+ this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
+ if (value === "myVault" && isExportAttachmentsEnabled) {
+ this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
+ }
+ });
+
merge(
this.exportForm.get("format").valueChanges,
this.exportForm.get("fileEncryptionType").valueChanges,
@@ -322,22 +336,6 @@ export class ExportComponent implements OnInit, OnDestroy, AfterViewInit {
takeUntil(this.destroy$),
)
.subscribe();
-
- combineLatest([
- this.exportForm.controls.vaultSelector.valueChanges,
- this.isExportAttachmentsEnabled$,
- ])
- .pipe(takeUntil(this.destroy$))
- .subscribe(([value, isExportAttachmentsEnabled]) => {
- this.organizationId = value !== "myVault" ? value : undefined;
- if (value === "myVault" && isExportAttachmentsEnabled) {
- if (!this.formatOptions.some((option) => option.value === "zip")) {
- this.formatOptions.push({ name: ".zip (with attachments)", value: "zip" });
- }
- } else {
- this.formatOptions = this.formatOptions.filter((option) => option.value !== "zip");
- }
- });
}
ngAfterViewInit(): void {
From 740d0251b8d57ceab78443f6023e86078ecfa481 Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:08:03 +0100
Subject: [PATCH 47/66] [PM-19368]Add new collection from individual vault is
not displaying Upgrade option rather than Save (#13965)
* Resolve the pop up issue and update button
* Rename a method properly
---
.../collection-dialog.component.ts | 29 ++++++++++++++++---
1 file changed, 25 insertions(+), 4 deletions(-)
diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
index 88746dc708b..1214c0ca411 100644
--- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
+++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts
@@ -13,6 +13,8 @@ import {
Subject,
switchMap,
takeUntil,
+ tap,
+ filter,
} from "rxjs";
import { first } from "rxjs/operators";
@@ -189,10 +191,29 @@ export class CollectionDialogComponent implements OnInit, OnDestroy {
this.formGroup.updateValueAndValidity();
}
- this.organizationSelected.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((_) => {
- this.organizationSelected.markAsTouched();
- this.formGroup.updateValueAndValidity();
- });
+ this.organizationSelected.valueChanges
+ .pipe(
+ tap((_) => {
+ if (this.organizationSelected.errors?.cannotCreateCollections) {
+ this.buttonDisplayName = ButtonType.Upgrade;
+ } else {
+ this.buttonDisplayName = ButtonType.Save;
+ }
+ }),
+ filter(() => this.organizationSelected.errors?.cannotCreateCollections),
+ switchMap((value) => this.findOrganizationById(value)),
+ takeUntil(this.destroy$),
+ )
+ .subscribe((org) => {
+ this.orgExceedingCollectionLimit = org;
+ this.organizationSelected.markAsTouched();
+ this.formGroup.updateValueAndValidity();
+ });
+ }
+
+ async findOrganizationById(orgId: string): Promise {
+ const organizations = await firstValueFrom(this.organizations$);
+ return organizations.find((org) => org.id === orgId);
}
async loadOrg(orgId: string) {
From 646c7198aa2bb7953b6ee69bb619acf2ea9d1b8e Mon Sep 17 00:00:00 2001
From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Date: Mon, 31 Mar 2025 15:10:36 +0100
Subject: [PATCH 48/66] Changes to add validation (#13762)
---
.../app/billing/organizations/change-plan-dialog.component.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
index dc748e9ee41..a11858f3be8 100644
--- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
+++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts
@@ -733,6 +733,7 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy {
submit = async () => {
if (this.taxComponent !== undefined && !this.taxComponent.validate()) {
+ this.taxComponent.markAllAsTouched();
return;
}
From 0311681803f4e97d3ceb15c15efcd63655c3b95c Mon Sep 17 00:00:00 2001
From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
Date: Mon, 31 Mar 2025 16:55:04 +0200
Subject: [PATCH 49/66] Fix filename not including "_encrypted_" when selecting
encrypted vault exports (#14066)
Co-authored-by: Daniel James Smith
---
.../src/services/individual-vault-export.service.ts | 2 +-
.../vault-export-core/src/services/org-vault-export.service.ts | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
index 765de042d32..489a28b4c79 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts
@@ -250,7 +250,7 @@ export class IndividualVaultExportService
return {
type: "text/plain",
data: JSON.stringify(jsonDoc, null, " "),
- fileName: ExportHelper.getFileName("", "json"),
+ fileName: ExportHelper.getFileName("", "encrypted_json"),
} as ExportedVaultAsString;
}
diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts
index f9ecd778c23..8f6494edb70 100644
--- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts
+++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts
@@ -109,7 +109,7 @@ export class OrganizationVaultExportService
data: onlyManagedCollections
? await this.getEncryptedManagedExport(organizationId)
: await this.getOrganizationEncryptedExport(organizationId),
- fileName: ExportHelper.getFileName("org", "json"),
+ fileName: ExportHelper.getFileName("org", "encrypted_json"),
} as ExportedVaultAsString;
}
From 22039d038d7d88c92247c856f9358fb8e11c018b Mon Sep 17 00:00:00 2001
From: Bernd Schoolmann
Date: Mon, 31 Mar 2025 16:58:02 +0200
Subject: [PATCH 50/66] [PM-3475] Remove deprecated keys (#13266)
* Remove deprecated keys
* Fix cli build
* Fix build
---
.../browser/src/background/main.background.ts | 2 -
.../service-container/service-container.ts | 2 -
.../src/services/jslib-services.module.ts | 2 -
.../abstractions/pin.service.abstraction.ts | 13 +-
.../pin/pin.service.implementation.ts | 171 ++----------------
.../common/services/pin/pin.service.spec.ts | 164 ++---------------
.../services/master-password.service.ts | 13 --
.../services/vault-timeout.service.ts | 1 -
.../platform/abstractions/state.service.ts | 8 -
.../src/platform/models/domain/account.ts | 2 -
.../src/platform/services/state.service.ts | 41 -----
.../src/abstractions/key.service.ts | 8 -
libs/key-management/src/key.service.spec.ts | 8 -
libs/key-management/src/key.service.ts | 24 ---
14 files changed, 32 insertions(+), 427 deletions(-)
diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts
index cae554c872c..eec07b5b1ed 100644
--- a/apps/browser/src/background/main.background.ts
+++ b/apps/browser/src/background/main.background.ts
@@ -655,9 +655,7 @@ export default class MainBackground {
this.kdfConfigService,
this.keyGenerationService,
this.logService,
- this.masterPasswordService,
this.stateProvider,
- this.stateService,
);
this.keyService = new DefaultKeyService(
diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts
index 5bc07f63c32..6a4651bcd5a 100644
--- a/apps/cli/src/service-container/service-container.ts
+++ b/apps/cli/src/service-container/service-container.ts
@@ -436,9 +436,7 @@ export class ServiceContainer {
this.kdfConfigService,
this.keyGenerationService,
this.logService,
- this.masterPasswordService,
this.stateProvider,
- this.stateService,
);
this.keyService = new KeyService(
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index 37220b5195d..6334e5815d6 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -1173,9 +1173,7 @@ const safeProviders: SafeProvider[] = [
KdfConfigService,
KeyGenerationServiceAbstraction,
LogService,
- MasterPasswordServiceAbstraction,
StateProvider,
- StateServiceAbstraction,
],
}),
safeProvider({
diff --git a/libs/auth/src/common/abstractions/pin.service.abstraction.ts b/libs/auth/src/common/abstractions/pin.service.abstraction.ts
index 0d0f29dff40..16550888b94 100644
--- a/libs/auth/src/common/abstractions/pin.service.abstraction.ts
+++ b/libs/auth/src/common/abstractions/pin.service.abstraction.ts
@@ -1,4 +1,4 @@
-import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
+import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { UserId } from "@bitwarden/common/types/guid";
import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfig } from "@bitwarden/key-management";
@@ -90,17 +90,6 @@ export abstract class PinServiceAbstraction {
*/
abstract clearUserKeyEncryptedPin(userId: UserId): Promise;
- /**
- * Gets the old MasterKey, encrypted by the PinKey (formerly called `pinProtected`).
- * Deprecated and used for migration purposes only.
- */
- abstract getOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise;
-
- /**
- * Clears the old MasterKey, encrypted by the PinKey.
- */
- abstract clearOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise;
-
/**
* Makes a PinKey from the provided PIN.
*/
diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts
index 0f6ac05f381..99fb725c295 100644
--- a/libs/auth/src/common/services/pin/pin.service.implementation.ts
+++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts
@@ -4,11 +4,9 @@ import { firstValueFrom, map } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
-import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString, EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
@@ -18,7 +16,7 @@ import {
UserKeyDefinition,
} from "@bitwarden/common/platform/state";
import { UserId } from "@bitwarden/common/types/guid";
-import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key";
+import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { KdfConfig, KdfConfigService } from "@bitwarden/key-management";
import { PinServiceAbstraction } from "../../abstractions/pin.service.abstraction";
@@ -73,19 +71,6 @@ export const USER_KEY_ENCRYPTED_PIN = new UserKeyDefinition(
},
);
-/**
- * The old MasterKey, encrypted by the PinKey (formerly called `pinProtected`).
- * Deprecated and used for migration purposes only.
- */
-export const OLD_PIN_KEY_ENCRYPTED_MASTER_KEY = new UserKeyDefinition(
- PIN_DISK,
- "oldPinKeyEncryptedMasterKey",
- {
- deserializer: (jsonValue) => jsonValue,
- clearOn: ["logout"],
- },
-);
-
export class PinService implements PinServiceAbstraction {
constructor(
private accountService: AccountService,
@@ -94,9 +79,7 @@ export class PinService implements PinServiceAbstraction {
private kdfConfigService: KdfConfigService,
private keyGenerationService: KeyGenerationService,
private logService: LogService,
- private masterPasswordService: MasterPasswordServiceAbstraction,
private stateProvider: StateProvider,
- private stateService: StateService,
) {}
async getPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise {
@@ -190,9 +173,7 @@ export class PinService implements PinServiceAbstraction {
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
-
const pinKey = await this.makePinKey(pin, email, kdfConfig);
-
return await this.encryptService.encrypt(userKey.key, pinKey);
}
@@ -242,20 +223,6 @@ export class PinService implements PinServiceAbstraction {
return await this.encryptService.encrypt(pin, userKey);
}
- async getOldPinKeyEncryptedMasterKey(userId: UserId): Promise {
- this.validateUserId(userId, "Cannot get oldPinKeyEncryptedMasterKey.");
-
- return await firstValueFrom(
- this.stateProvider.getUserState$(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, userId),
- );
- }
-
- async clearOldPinKeyEncryptedMasterKey(userId: UserId): Promise {
- this.validateUserId(userId, "Cannot clear oldPinKeyEncryptedMasterKey.");
-
- await this.stateProvider.setUserState(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, null, userId);
- }
-
async makePinKey(pin: string, salt: string, kdfConfig: KdfConfig): Promise {
const pinKey = await this.keyGenerationService.deriveKeyFromPassword(pin, salt, kdfConfig);
return (await this.keyGenerationService.stretchKey(pinKey)) as PinKey;
@@ -264,23 +231,13 @@ export class PinService implements PinServiceAbstraction {
async getPinLockType(userId: UserId): Promise {
this.validateUserId(userId, "Cannot get PinLockType.");
- /**
- * We can't check the `userKeyEncryptedPin` (formerly called `protectedPin`) for both because old
- * accounts only used it for MP on Restart
- */
const aUserKeyEncryptedPinIsSet = !!(await this.getUserKeyEncryptedPin(userId));
const aPinKeyEncryptedUserKeyPersistentIsSet =
!!(await this.getPinKeyEncryptedUserKeyPersistent(userId));
- const anOldPinKeyEncryptedMasterKeyIsSet =
- !!(await this.getOldPinKeyEncryptedMasterKey(userId));
- if (aPinKeyEncryptedUserKeyPersistentIsSet || anOldPinKeyEncryptedMasterKeyIsSet) {
+ if (aPinKeyEncryptedUserKeyPersistentIsSet) {
return "PERSISTENT";
- } else if (
- aUserKeyEncryptedPinIsSet &&
- !aPinKeyEncryptedUserKeyPersistentIsSet &&
- !anOldPinKeyEncryptedMasterKeyIsSet
- ) {
+ } else if (aUserKeyEncryptedPinIsSet && !aPinKeyEncryptedUserKeyPersistentIsSet) {
return "EPHEMERAL";
} else {
return "DISABLED";
@@ -302,7 +259,7 @@ export class PinService implements PinServiceAbstraction {
case "DISABLED":
return false;
case "PERSISTENT":
- // The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey or OldPinKeyEncryptedMasterKey set.
+ // The above getPinLockType call ensures that we have either a PinKeyEncryptedUserKey set.
return true;
case "EPHEMERAL": {
// The above getPinLockType call ensures that we have a UserKeyEncryptedPin set.
@@ -326,31 +283,21 @@ export class PinService implements PinServiceAbstraction {
try {
const pinLockType = await this.getPinLockType(userId);
- const requireMasterPasswordOnClientRestart = pinLockType === "EPHEMERAL";
- const { pinKeyEncryptedUserKey, oldPinKeyEncryptedMasterKey } =
- await this.getPinKeyEncryptedKeys(pinLockType, userId);
+ const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedKeys(pinLockType, userId);
const email = await firstValueFrom(
this.accountService.accounts$.pipe(map((accounts) => accounts[userId].email)),
);
const kdfConfig = await this.kdfConfigService.getKdfConfig();
- let userKey: UserKey;
-
- if (oldPinKeyEncryptedMasterKey) {
- userKey = await this.decryptAndMigrateOldPinKeyEncryptedMasterKey(
- userId,
- pin,
- email,
- kdfConfig,
- requireMasterPasswordOnClientRestart,
- oldPinKeyEncryptedMasterKey,
- );
- } else {
- userKey = await this.decryptUserKey(userId, pin, email, kdfConfig, pinKeyEncryptedUserKey);
- }
-
+ const userKey: UserKey = await this.decryptUserKey(
+ userId,
+ pin,
+ email,
+ kdfConfig,
+ pinKeyEncryptedUserKey,
+ );
if (!userKey) {
this.logService.warning(`User key null after pin key decryption.`);
return null;
@@ -394,109 +341,23 @@ export class PinService implements PinServiceAbstraction {
}
/**
- * Creates a new `pinKeyEncryptedUserKey` and clears the `oldPinKeyEncryptedMasterKey`.
- * @returns UserKey
- */
- private async decryptAndMigrateOldPinKeyEncryptedMasterKey(
- userId: UserId,
- pin: string,
- email: string,
- kdfConfig: KdfConfig,
- requireMasterPasswordOnClientRestart: boolean,
- oldPinKeyEncryptedMasterKey: EncString,
- ): Promise {
- this.validateUserId(userId, "Cannot decrypt and migrate oldPinKeyEncryptedMasterKey.");
-
- const masterKey = await this.decryptMasterKeyWithPin(
- userId,
- pin,
- email,
- kdfConfig,
- oldPinKeyEncryptedMasterKey,
- );
-
- const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey({ userId: userId });
-
- const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
- masterKey,
- userId,
- encUserKey ? new EncString(encUserKey) : undefined,
- );
-
- const pinKeyEncryptedUserKey = await this.createPinKeyEncryptedUserKey(pin, userKey, userId);
- await this.storePinKeyEncryptedUserKey(
- pinKeyEncryptedUserKey,
- requireMasterPasswordOnClientRestart,
- userId,
- );
-
- const userKeyEncryptedPin = await this.createUserKeyEncryptedPin(pin, userKey);
- await this.setUserKeyEncryptedPin(userKeyEncryptedPin, userId);
-
- await this.clearOldPinKeyEncryptedMasterKey(userId);
-
- return userKey;
- }
-
- // Only for migration purposes
- private async decryptMasterKeyWithPin(
- userId: UserId,
- pin: string,
- salt: string,
- kdfConfig: KdfConfig,
- oldPinKeyEncryptedMasterKey?: EncString,
- ): Promise {
- this.validateUserId(userId, "Cannot decrypt master key with PIN.");
-
- if (!oldPinKeyEncryptedMasterKey) {
- const oldPinKeyEncryptedMasterKeyString = await this.getOldPinKeyEncryptedMasterKey(userId);
-
- if (oldPinKeyEncryptedMasterKeyString == null) {
- throw new Error("No oldPinKeyEncrytedMasterKey found.");
- }
-
- oldPinKeyEncryptedMasterKey = new EncString(oldPinKeyEncryptedMasterKeyString);
- }
-
- const pinKey = await this.makePinKey(pin, salt, kdfConfig);
- const masterKey = await this.encryptService.decryptToBytes(oldPinKeyEncryptedMasterKey, pinKey);
-
- return new SymmetricCryptoKey(masterKey) as MasterKey;
- }
-
- /**
- * Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral) and `oldPinKeyEncryptedMasterKey`
+ * Gets the user's `pinKeyEncryptedUserKey` (persistent or ephemeral)
* (if one exists) based on the user's PinLockType.
*
- * @remarks The `oldPinKeyEncryptedMasterKey` (formerly `pinProtected`) is only used for migration and
- * will be null for all migrated accounts.
* @throws If PinLockType is 'DISABLED' or if userId is not provided
*/
private async getPinKeyEncryptedKeys(
pinLockType: PinLockType,
userId: UserId,
- ): Promise<{ pinKeyEncryptedUserKey: EncString; oldPinKeyEncryptedMasterKey?: EncString }> {
+ ): Promise {
this.validateUserId(userId, "Cannot get PinKey encrypted keys.");
switch (pinLockType) {
case "PERSISTENT": {
- const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyPersistent(userId);
- const oldPinKeyEncryptedMasterKey = await this.getOldPinKeyEncryptedMasterKey(userId);
-
- return {
- pinKeyEncryptedUserKey,
- oldPinKeyEncryptedMasterKey: oldPinKeyEncryptedMasterKey
- ? new EncString(oldPinKeyEncryptedMasterKey)
- : undefined,
- };
+ return await this.getPinKeyEncryptedUserKeyPersistent(userId);
}
case "EPHEMERAL": {
- const pinKeyEncryptedUserKey = await this.getPinKeyEncryptedUserKeyEphemeral(userId);
-
- return {
- pinKeyEncryptedUserKey,
- oldPinKeyEncryptedMasterKey: undefined, // Going forward, we only migrate non-ephemeral version
- };
+ return await this.getPinKeyEncryptedUserKeyEphemeral(userId);
}
case "DISABLED":
throw new Error("Pin is disabled");
diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts
index 794d08b63b2..ebdc36219ef 100644
--- a/libs/auth/src/common/services/pin/pin.service.spec.ts
+++ b/libs/auth/src/common/services/pin/pin.service.spec.ts
@@ -1,11 +1,9 @@
import { mock } from "jest-mock-extended";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
-import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
-import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -15,14 +13,13 @@ import {
mockAccountServiceWith,
} from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
-import { MasterKey, PinKey, UserKey } from "@bitwarden/common/types/key";
+import { PinKey, UserKey } from "@bitwarden/common/types/key";
import { DEFAULT_KDF_CONFIG, KdfConfigService } from "@bitwarden/key-management";
import {
PinService,
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
PIN_KEY_ENCRYPTED_USER_KEY_EPHEMERAL,
- OLD_PIN_KEY_ENCRYPTED_MASTER_KEY,
USER_KEY_ENCRYPTED_PIN,
PinLockType,
} from "./pin.service.implementation";
@@ -31,7 +28,6 @@ describe("PinService", () => {
let sut: PinService;
let accountService: FakeAccountService;
- let masterPasswordService: FakeMasterPasswordService;
let stateProvider: FakeStateProvider;
const cryptoFunctionService = mock();
@@ -39,11 +35,9 @@ describe("PinService", () => {
const kdfConfigService = mock();
const keyGenerationService = mock();
const logService = mock();
- const stateService = mock